算法笔记 From Now To My Death

    • 前言
  • 初级算法
    • 1、两数之和
    • 7、整数反转
    • 9、回文数
    • 14、最长公共前缀
    • 27、移除元素【拷贝复制】
    • 28、实现strStr()【双指针】
    • 35、搜索插入位置
  • 中级算法
    • 2、两数相加【预先指针】
    • 3、无重复字符的最长子串【滑动窗口】
    • 5、最长回文子串【动态规划】
    • 6、Z字形变换
    • 8、字符串转整数【模拟C/C++中的atoi函数】
    • 11、盛水最多的容器【双层for-->双指针优化】
    • 15、三数之和【排序+双指针】
    • 16、最接近的三数之和【排序+双指针】
    • 17、电话号码的数字组合【回溯递归】
    • 18、四数之和【排序+双指针】
    • 19、删除链表的倒数第n个节点【巧妙的双指针】
    • 20、有效的括号【栈的使用】
    • 21、合并两个有序链表【递归】***
    • 22、括号生成【深度优先遍历 + 回溯递归】
    • 24、两两交换链表中的节点【回溯递归】
    • 29、两数相除【移位运算】
    • 31、下一个排列
    • 33、搜索旋转排序数组
    • 34、在排序数组中找出目标元素第一个和最后一个出现的位置。
    • 36、有效数独
    • 38、外观数列【递归】
    • 300、最长递增子序列【动态规划】
  • 高级算法
    • 4、寻找两个正序数组的中位数【归并排序、二分查找】
    • 23、合并K个升序链表【合并2个有序链表(方法二)升级版】
    • 25、翻转K个一组的链表
    • 30、串联所有单词的子串【滑动窗口】
    • 32、最长的有效括号【栈】
    • 37、解数独【回溯递归】

此篇以完结:为了抓住面试重点:转向剑指Offer每日一题系列。Click me forward to new article

前言

以前当兵的时候,每次搞30公里强行军都很累。于是我会在50斤的背囊上写着:行百里者半九十——>靠着这句话,即使腿抽筋着,我都能坚持自己完成下来。
现在,我同样用这句话来激励自己,天下没有难学的技术,只有半途而废的人。不要求我一定能成为技术大牛(毕竟这需要一定的天赋和机遇)
旦求无愧于自己。平凡而不平庸即可。

比昨天的自己更好一点,比明天的自己更差一点

初级算法

1、两数之和

给定一个数组nums,和一个整数目标值target。从数组中找出两个元素,他们的和 = target。
返回对应两个元素的数组下标。

public int[] twoSum(int[] nums, int target){Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length(); i ++){// 利用 【Map集合的containsKey API】
//  逆向思维: target = key + nums[i]
//       ===> map.key = target - nums[i]时 返回结果!
if(map.containsKey(target - nums[i])){return new int[]{map.get(target - nums[i]), i};
}
}
map.put(nums[i],i);
}
return null;

如果用双层for循环固然简单,但是时间复杂度为O(n^2)
解题思路:


7、整数反转

给你一个32位的有符号整数x,返回将x中的数字反转后端结果。
如果反转后的整数超过32位的有符号整数的范围[-2^31, 2^31 - 1]——> 返回 0

public int reverse(int x) {32位整数范围是:-2147483648 ~ 2147483647
为快速判断:只要将反转后的值,与最大值的最后两个数:十位 / 个位 进行比较即可
1、当反转后的值 大于十位之前的值时:无论它的各位数是几,都是越界的:214748365* 与 2147483647
2、当反转后的值 等于十位之前的值时:判断其个位 是否>最大值的个位:214748364* 与 2147483647int res = 0;  // 初始 0while(x!=0) {每次摘下当前 x 的个位int tmp = x%10;当摘下->放入 执行到214748364共9位时,即可进行判断(如果全部反转完再判断,则会抛出异常)因此,反转到最后2个(十位)时,如果大于了最大值的十位,则没必要比较个位了、如果反转到十位时,发现和最大值的十位及其之前的数都相等。则需要再反转一次:比较个位//判断是否 大于 最大32位整数if (res>214748364 || (res==214748364 && tmp>7)) {return 0;}//判断是否 小于 最小32位整数if (res<-214748364 || (res==-214748364 && tmp<-8)) {return 0;}// res:当前这一步while反转后的值res = res*10 + tmp; 将当前 x 的个位,放入反转后的值的个位中//  由于 原值 x 的末尾数字已经被取走:放入res中了。// 因此 原值x 就少扣除末尾的那一位。x /= 10;}return res;}

9、回文数

给你一个整数x,如果x是一个回文整数,返回true。否则返回false。
eg:123不是回文。121是回文
直接StringBuilder.reverse => 但是这样要额外创建对象、并且反转整个字符串来比较
实际上:只要反转x的前半段,然后与后半段比较即可、如11222211; 反转1122—>2211 == 后半段!

    private static boolean test(int x){if (x <= 0 || ( x % 10 == 0 && x != 0)){return false;}int revertedNum = 0;// 通过 % 和 /的方式,达到string字符串remove的效果// eg: 1122332211  ==> 每次从末尾摘除一位,赋给revertedNum后,x就要扣除一位// 最后 x = 11223 == revertedNum = 11223 退出循环!while (x > revertedNum){// 反转后的数 = 上次反转的数 * 10 + 本次 x 的末尾摘下的个位revertedNum = revertedNum * 10 + x % 10;// 本次x被摘下了个位,于是就x剩下 x / 10;// 这样只需要摘除x的一半长度时,即可判断是否为回文数x = x / 10;}// 如果x是奇数:那么退出循环的结果会是 x=1122 revertedNum=11223// 所以x == revertedNum / 10 时也为true;return x == revertedNum || x == revertedNum / 10;}

14、最长公共前缀

寻找一个字符串数组中的最长公共前缀,不存在则返回""

    private static String test(String[] arr){if (arr.length == 0){return "";}else if (arr.length == 1){return arr[0];}String commonString = "";String first = arr[0];int flag = 0;for (int i = 0; i < first.length(); i++) {for (int j = 1; j < arr.length; j++) {if (!arr[j].startsWith(first.substring(0, i))){flag = 1;break;}}if (flag == 1){break;}commonString = first.substring(0, i);}return commonString;}

27、移除元素【拷贝复制】

给你一个整数数组、和一个目标值val。移除该数组中所有值等于val的元素。返回移除后的长度。
要求:原地修改数组、不使用额外的空间。

 public static void main(String[] args) {int[] nums = {1,2,3,4,5,6};System.out.println(removeElement(nums,5));}private static int removeElement(int[] nums, int val){int result = 0;for (int i = 0; i < nums.length; i++) {//在原数组上:发现与val相同的元素,则跳过// 与val不同的元素,则放入数组前面,保存下来。并且长度++if (nums[i] != val){nums[result] = nums[i];result++;}}return result;}

28、实现strStr()【双指针】

给你两个字符串haystack和needle,请你再haystack字符串中找出needle字符串出现的第一个位置
如果不存在,则返回 -1; 当needle字符串为空时,应该返回0;
这与C语言定义的strStr()函数以及Java定义的indexOf()函数相当
双指针在数组遍历中非常非常地常见

    private static int indexOf(String haystack, String needle){int result = 0;if (haystack.equals("") || needle.length() > haystack.length()){return -1;}if (needle.equals("")){return 0;}int left = 0, right = needle.length();while (right < haystack.length()){String substring = haystack.substring(left, right);if (substring.equals(needle)){return left;}left++;right++;}return result;}

35、搜索插入位置

很简单的一题、没啥可说的。
给你一个无重复元素的升序数组、给你一个target、找出target的插入位置。如果已有target则返回索引

    private static int searchInsertPosition(int[] nums, int target){if (target < nums[0]){return 0;}if (target > nums[nums.length - 1]){return nums.length;}int left = 0, right = nums.length - 1;int mid = 0;while (left < right){mid = (left + right) / 2;if (nums[mid] == target){return mid;}if (nums[mid] < target){left = mid + 1;}else {right = mid - 1;}}return mid;}

==============================================================================

中级算法

2、两数相加【预先指针】

给你两个【非空】链表,表示两个非负整数。他们的每位数字都是按照【逆序】的方式存储的,
并且每个节点只能存储【一位】数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字0之外,这两个数都不会以0开头。
示例:l1 = [2, 4, 3] 、 l2 = [5, 6, 4]、 342 + 465 = 708 、、 return [7, 0, 8]

先看看我的解题思路:代码有点复杂
就是用简单的:每一位与每一位相加,>0则往上一位进1。我这里没有给短的List用0补全。
而LetCode大佬的解法是:把短的这个List用0补全——>构造出 l1长度 == l2,然后进行进位运算

 public ListNode addTwoNumbers(ListNode l1,ListNode l2){// 用于存储最后结果的list链表LinkedList<Integer> list = new LinkedList<>();//存储每次要进位的数int z = 0;//  如果传进来的l1 > l2 则交换一下位置if (l1.size() > l2.size()){return addTwoNumbers(l2,l1);}Iterator i1 = l1.iterator();Iterator i2 = l2.iterator();//  小的链表驱动大的表while (i1.hasNext()){// 取出l1的尾元素int last1 = l1.removeLast();// 取出l2的尾元素int last2 = l2.removeLast();//  计算两之和int sum = last1 + last2 + z;//  求出余数int y = sum % 10;//   求出进位的数  18则进位1  即 z = 1z = sum / 10;//  余数即可放入return的链表中了list.add(y);}//  当小的链表都取完了之后,直接取大的链表剩余的部分即可。 for (int i = 0; i < l2.size(); i++) {int last = l2.removeLast();// 注意也要加上之前余留的进位数 zint sum = last + z;int y = sum % 10;z = sum / 10;//  把余数加到return的list中list.add(y);}//  最后,如果进位!=0,则说明还没加完,把最后这个进位加到末尾即可。if (y != 0){list.add(z);}return list;
}

3、无重复字符的最长子串【滑动窗口】

给定一个字符串s,请你找出其中不含有重复字符的【最长子串】的长度
例如:输入s = “pwwkew” 输出3

//  博主的渣渣解题方法: 时间复杂度为O(m)public int getMaxLengthOfString(String s){String result = "";int max = 1;for (int i = 0; i < s.length(); i++) {String c = String.valueOf(s.charAt(i));System.out.println("char[" + i + "] -->" + c);System.out.println("result --pre-->" + result);// 判断目前筛选出的无重复字符的字符串result中,是否含有将要比对的这个字符 cif (result.contains(c)){System.out.println("此次result为" + result + ",发现重复字符:" + c);max = Math.max(max,result.length());System.out.println("目前筛选出的不重复字符串result的最大长度为:" + max);// 重置之前筛选出的result为当前发现的这个重复字符:继续往后筛选比对result = c;System.out.println("重置result! 重置后的result为:" + result);}else {System.out.println("当前result中不包含"+ c +",将" + c + "加入result中...");result = result + c;System.out.println("result --post-->" + result);}System.out.println("------------------------------>");}System.out.println("筛选完毕,返回结果------->");return max;}

LetCode大佬的【滑动窗口】算法
窗口:内含无重复字符的最长子串。每次找到重复字符,指针滑动到重复字符处!
坚持寻找无重复字符找了好久都没重复!突然发现了一个重复的!前功尽弃!在这里重新开始

    private static int getMaxLengthOfString(String s){int n = s.length(), ans = 0;//  key为字符 value为下标+1// map中存着目前已经扫描到的字符及其下标Map<Character, Integer> map = new HashMap<>();for (int end = 0, start = 0; end < n; end++) {char alpha = s.charAt(end);if (map.containsKey(alpha)) {// 如果map中已经包含的这个字符:即发现了重复字符//  则将起始指针移至重复字符的下标处start = Math.max(map.get(alpha), start);}// 不包含则将本次的长度置为新的最大长度ansans = Math.max(ans, end - start + 1);// 并将本次扫描过的字符放到map中, 以便下次contains比较map.put(s.charAt(end), end + 1);}return ans;}

5、最长回文子串【动态规划】

给你一个字符串s,找到s中的最长回文子串:即字符串关于中心对称(左=右)
如:s=“babad” return “bab”; s=“cbbd” return “bb”;

解法:动态规划!为减少重复计算:
每次都需要对内层字符串是否为回文串进行重复判断
将内层字符串是否为回文串缓存起来!这样就不用重复判断了
用一个boolean dp[l][r] (类似Redis,缓存着上一次的回文串) 表示字符串从i -> j是否为回文子串。
要判断i->j为回文子串,dp[l][r]=true===>即要判断它的前一位是否为回文子串dp[l-1][r-1]=true

    public String longestPalindrome(String s) {// 如果s的长度为1、那回文串就是她本身if (s == null || s.length() < 2) {return s;}int strLen = s.length();int maxStart = 0;  //最长回文串的起点int maxEnd = 0;    //最长回文串的终点int maxLen = 1;  //最长回文串的长度// 类似redis:缓存着上一轮的最大回文子串boolean[][] dp = new boolean[strLen][strLen];// r指针从1开始 -> 末尾for (int r = 1; r < strLen; r++) {// l指针从0开始 -> r   ==> 这样循环下来就能从左到右把所有可能性都遍历一次for (int l = 0; l < r; l++) {// 如果本次l=r,那么就要看看他们的前一位是否也为回文子串// 1.如果r-l<=2即长度为3的时候,那就不用判断dp=true。直接为true!eg:babif (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {// 判断成功!将本次true记录一下dp[l][r] = true;// 并且如果本次长度合法、则记录头指针和尾指针、用于返回最后结果if (r - l + 1 > maxLen) {maxLen = r - l + 1;maxStart = l;maxEnd = r;}}}}return s.substring(maxStart, maxEnd + 1);}

6、Z字形变换

将一个给定字符串s根据给定的行数numRows,从上往下、从左往右进行Z字排序
eg:输入PAYPALISHIRING,numRows=3时。排列为:
之后:将该Z字形排列从左往右逐行读取,产生新的字符串:PAHNAPLSIIGYIR

解答:
何时Z字形字符的方向开始变化!
1、每次在行数 = 0 和 numRows - 1 (即两端处方向发生变换)
2、rowNow = 0 时下一个元素所在行号:为本次行号 + 1,为numRows - 1时:回头:为本次行号 - 1
3、定义一个boolean的flag来标志下一行行号是该 + 1 还是 - 1 ?
用什么数据结构来存储每一行读出的字符?并方便最后结果的拼接?
4、定义一个String类型数组、长度为numRows、数组下标0对应第0行读出的字符、下标1对应第一行

    private static String convert(String s, int numRows){if (numRows == 1){return s;}// 这里按道理是直接取(行数)len = numRows,但要考虑特殊情况。// 如:s的长度比numRows还小时,直接输出s即可int len = Math.min(s.length(), numRows);// 声明一个字符串数组:下标为 0 1 2的分别存储第0 1 2行的字符// 最后再将这个数组中的字符串依次拼接即可String rows[] = new String[len];for (int row = 0; row < len; row++) {// 先通过for循环把需要的几行数组元素初始化为空串rows[row] = "";}// down为false时,为向右上走(i-1)。true为向下走(i+1)boolean down = false;// 定义当前所在行int rowNow = 0;// 开始顺序遍历s字符串:for (int i = 0; i < s.length(); i++) {// 第0个放第0行中,第1个放第1行...遇到方向变化则回头rows[rowNow] += s.charAt(i);// 在当前行数为0;或为numRows - 1:即在两端口时,需要变换方向if (rowNow == 0 || rowNow == numRows -1){down = ! down;}// 下次循环的行数rowNow是根据转向标志flag来判断是+1还是-1;rowNow += down ? 1 : -1;}// 至此,String数组中每个元素都存着对应下标行读取出的字符//  因此把他们从0->len拼接起来即可String result = "";for (int i = 0; i < len; i++) {System.out.println("--第" + i + "行数据为:-->" + rows[i]);result += rows[i];}return result;}

8、字符串转整数【模拟C/C++中的atoi函数】

1、第一步:丢弃读入字符串无用的前导空格:" 1" => 1
2、第二步:检查下一符号是"+“还是”-",并作为结果正负的依据:" -1" => -1
3、第三步、读入下一非数字字符,直到到达下一非数字字符,往后的其余字符部分则会忽略:
" -0011231 asdada" => -11231 ———— “words and 987” => “0”:因为第三步未解析到数字,结果为0
4、 如果超过Integer的最大和最小值,则取最大最小值
关键点:如何判断char字符是否为a-z或0-9?根据ASCII码:int 值 = char(x) - ‘0’

public static int myAtoi(String str) {int len = str.length();// str.charAt(i) 方法回去检查下标的合法性,一般先转换成字符数组char[] charArray = str.toCharArray();// 1、去除前导空格int index = 0;while (index < len && charArray[index] == ' ') {index++;}// 2、如果已经遍历完成(针对极端用例 "      ")if (index == len) {return 0;}// 3、如果出现符号字符,仅第 1 个有效,并记录正负int sign = 1;char firstChar = charArray[index];if (firstChar == '+') {index++;} else if (firstChar == '-') {index++;sign = -1;}// 4、将后续出现的数字字符进行转换// 不能使用 long 类型,这是题目说的int res = 0;while (index < len) {char currChar = charArray[index];// 4.1 先判断不合法的情况、通过字符的ACSII码来判断是否是合法数字if (currChar > '9' || currChar < '0') {break;}// 题目中说:环境只能存储 32 位大小的有符号整数,// 因此,需要提前判断乘以 10 以后是否越界 (本次char作为个位 + res*10为新的结果)if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && (currChar - '0') > Integer.MAX_VALUE % 10)) {return Integer.MAX_VALUE;}if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && (currChar - '0') > -(Integer.MIN_VALUE % 10))) {return Integer.MIN_VALUE;}// 4.2 合法的情况下,才考虑转换,每一步都把符号位乘进去// currChar - '0' 的 ASCII码的结果:正好就是数字的值:56(8) - 48(0) = 8res = res * 10 + sign * (currChar - '0');index++;}return res;}public static void main(String[] args) {String str = "2147483646";int res = myAtoi(str);System.out.println(res);System.out.println(Integer.MAX_VALUE);System.out.println(Integer.MIN_VALUE);}

11、盛水最多的容器【双层for–>双指针优化】

给你一串数字:int[] arr = {1, 8, 6, 2, 5, 4, 8, 3, 7}; 输出最大水容量 = 49
定义水容量为:两数字间的距离 * Math.min(数字1,数字2) ==> 即:较小的数字影响水的高度

1、简单解法: 双层for循环寻找:时间复杂度O(n^2)
2、考虑是否可以优化双层for?因为双层for是暴力解法,时间比较慢?
==>双指针 ==> 用双指针的话! 关键点就化为 ⇒ 每次循环结束:改移动哪个指针?

    // 凡是遇到【双层for循环的问题】==>考虑是否可用【双指针优化】private static int test(int[] arr){int capacity = 0;int len = arr.length;int i = 0, j = len - 1, hi = 0, hj = 0;// 从左右两边两个指针往中间遍历,时间复杂度O(n)。双层for则是O(n^2)while (i < j){hi = arr[i];hj = arr[j];capacity = Math.max(capacity, (j - i) * Math.min(hi,hj));// 这是一个数学规律:由水容量公式 = h * Math.min(hi,hj)// 因此,只要移动本次较短的那个木板,因为是他限制了我的水容量// 移动短的木板,尝试去找到比他更长的木板来提升水容量!// 假设移动的是较长的木板,两种情况:// 1.长木板找到更长的!但水容量是由短木板限制的,// Math.min(hi,hj)不变,而水桶宽度缩短 ==>必然造成水容量下降!// 2.长木板找到比原先更短或一样长的!new Hight <= old Hight// 而水桶宽度缩短 ==> 水容量也必然下降===> 因此,这就是我们要找的条件:【每次循环结束:该移动较短的木板】if (hi > hj){j --;}else {i ++;}}return capacity;}

15、三数之和【排序+双指针】

给你一个数组nums = [-1,0,1,2,-1,-4]; 找出三数之和 = 0;并且不重复的所有组合
输出:[[-1,-1,2],[-1,0,1]];

关键点:1、将数组进行排序–>因为三数之和=0;要求有负有正,L < R
2、 三个数:固定一个数!剩余两个数使用双指针!
3、左右指针:何时移动左、何时移动右。

    public static List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> ans = new ArrayList();int len = nums.length;if(nums == null || len < 3) {return ans;}Arrays.sort(nums); // 排序for (int i = 0; i < len ; i++) {if(nums[i] > 0) {break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环}//每一轮循环定下first第一个数后。// 就会将它后面的L(i+1) ~ R(len-1)范围内,所有符合条件的组合都筛选出来!// 假设i=0时。值为 -1。那么所有满足L+R+i(value)=0的组合就已经在本次循环查出来了!// i=1时,值如果和上一次i(i=0)相同,本次循环i=1 < i=0。L和R的检索范围小于上一次的范围// 由于上一次已经查出了所有满足条件的组合。因此这一次范围更小的循环必然重复if(i > 0 && nums[i] == nums[i-1]) {continue; // 去重}int L = i+1;int R = len-1;while(L < R){int sum = nums[i] + nums[L] + nums[R];if(sum == 0){// 如果本次满足要求。则添加到结果中ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
//这里为什么是这样去除L和R的重复值的?
// while(L<R)这个循环是在外层for i(第一个数)的内嵌循环!
// 在第一个数i 相同的情况下。L和R只要有一个数确定了,另一个数也就确定了!
// 也就是说,本次循环i是相同的,只要L和R有一个数重复了,那么结果必然重复!因此L R都需要去重// 为了下一次计算出的sum不是重复值!要将L指针移动到下一个非重复值处while (L<R && nums[L] == nums[L+1]) {L++; // 去重}// 为了下一次计算出的sum不是重复值!要将R指针移动到下一个非重复值处while (L<R && nums[R] == nums[R-1]) {R--; // 去重}L++;R--;}// 本轮总和<0,说明正数要多一些 即L++else if (sum < 0) {L++;// 本轮总和>0,说明正数要少一些 即R--} else if (sum > 0) {R--;}}}return ans;}

16、最接近的三数之和【排序+双指针】

给的一个数组nums(lenth > 3)和target。找出nums中最接近target的三个数的和
eg:nums = [-1,2,1,-4] target = 1。返回结果: 1

解法:同题:15

       public static int threeSum(int[] nums,int target) {Arrays.sort(nums); // 排序int distance = Math.abs(target - nums[0] + nums[1] + nums[2]);int result = 0;for (int i = 0; i < nums.length ; i++) {int L = i+1;int R = nums.length-1;while(L < R){int now = nums[i] + nums[L] + nums[R];if(Math.abs(target - now) < distance){// 如果本次满足要求distance = Math.abs(target - now);result = now;L++;R--;}// 本轮总和<target,说明正数要多一些 即L++else if (now < target) {L++;// 本轮总和>target,说明正数要少一些 即R--} else if (now > target) {R--;}}}return result;}

17、电话号码的数字组合【回溯递归】

给定一个数字组合(2-9):输出其所有的字母组合
根节点为2时、对应有a、b、c、三个子节点。三个子节点又都拥有d e f 子子节点

时间复杂度:O(3^m ✖ 4^n)

    private static List<String> result = new ArrayList<String>();private static StringBuilder combination = new StringBuilder();private static Map<Character, String> phoneMap = new HashMap<Character, String>() {{put('2', "abc");put('3', "def");put('4', "ghi");put('5', "jkl");put('6', "mno");put('7', "pqrs");put('8', "tuv");put('9', "wxyz");}};public static void main(String[] args) {System.out.println(letterresult("23"));}public static List<String> letterresult(String digits) {// 排空if (digits.length() == 0) {return result;}// 回溯:递归调用。backtrack(digits, 0);return result;}private static void backtrack(String digits, int index) {// 如果本次已经检索到最底层了!eg:23 length=2 index = 2时,// 说明是第三层目录了。而总共就两层,所以这次应该回溯// 即满足回溯条件。则输出这次结果 result.addif (index == digits.length() && combination != null) {System.out.println("达到底层,返回结果:" + combination.toString());result.add(combination.toString());} else {char digit = digits.charAt(index); // "23" -> 拿到 2String letters = phoneMap.get(digit); //  拿到2对应的字母abcint lettersCount = letters.length(); // abc的长度为3for (int i = 0; i < lettersCount; i++) { // 以abc为一级目录进行树形搜索System.out.println("添加前:" + combination.toString());System.out.println("添加combination:" + letters.charAt(i));combination.append(letters.charAt(i)); // 把a(本次层级)添加进combinationSystem.out.println("添加后:" + combination.toString());// 递归调用时、又会遍历index = 1 即二级目录:"3"// 对应字母def。将d e f分别与a组合。 combination.append(letters.charAt(i));// 得出 ad ae afbacktrack(digits, index + 1);//  --> a的分支(共两层)遍历完后,会在第三层时判断满足条件,回溯 result.add(combination.toString());// 从而:本分支backtrack方法递归结束了,此时执行下一步: deleteCharAt只删除本层(index)。保留父层级System.out.println("清空前:" + combination.toString());combination.deleteCharAt(index);System.out.println("清空后:" + combination.toString());}}}

18、四数之和【排序+双指针】

给你n个整数组成的数组nums、和一个目标值target!找出满足条件的四元数组!
条件:1、a b c d各不相同。2、nums[a] + nums[b] + nums[c] + nums[d] == target
eg:输入nums = [1, 0, -1, 0, -2, 2]。target = 0
输出:[[-2, ,1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]

参照第16题

    public static void main(String[] args) {int[] arr = {1,0,-1,0,-2,2};test(arr,0).forEach(System.out::println);}private static List<List<Integer>> test(int[] nums, int target){List<List<Integer>> result = new ArrayList<>();int len = nums.length;if (nums == null || len <= 3){return null;}Arrays.sort(nums);for (int i = 0; i < len - 3; i++) {if(nums[i] > 0) {break;}if (i > 0 && nums[i] == nums[i-1]){continue;}int LL = i + 1;for (; LL < len - 2; LL++) {if (LL > 0 && nums[LL] == nums[LL-1]){continue;}int L = LL + 1;int R = len - 1;while (L < R){int sum = nums[i] + nums[LL] + nums[L] + nums[R];if (sum == target){result.add(Arrays.asList(nums[i],nums[LL],nums[L],nums[R]));}if (nums[L] == nums[L + 1]){L++;}if (nums[R] == nums[R - 1]){R--;}//注意!移动双指针中L或R指针 的条件是什么?if (target > sum){L++;}else {R--;}}}}return result;}

19、删除链表的倒数第n个节点【巧妙的双指针】

删除链表的倒数第n个节点(注意:我们不知道链表的长度、
链表不像数组集合ArrayList那样,可以通过索引直接删除,链表可不行)

思路:L和R指针初始时,为一个new出来的空节点。指向链表头节点。
1、先移动R指针。移动n次。而后同时移动L和R,直至R到达链表末尾:R.next == null;
——>这样一来:保证了L指针和R指针之间间隔了n个节点。那么当R为尾节点时:L.next就是要删除的节点

private static ListNode removeNthFromEnd(ListNode head, int n){if (head == null){return null;}ListNode pointer = new ListNode(0);pointer.next = head;//初始化左右两个节点。使其指向头结点ListNode L = pointer, R = pointer;while (n != 0){//单独移动R指针,移动n次。R = R.next;n--;}此时L.next就是以R开始的倒数第n个节点// 将R和L同时移动,直至R为尾节点while (R.next != null){R = R.next;L = L.next;}//此时:L所指节点L.next即为倒数第n位。删除L指向的节点L.next = L.next.next;//不能返回pointer,要返回pointer.next。//head头结点可能会当成第n个删除。所以不能直接返回head(虽然pointer.next指的就是head)return pointer.next;}static class ListNode {int val;ListNode next;   // 下一个节点ListNode(int x) { val = x; }  //赋值}

20、有效的括号【栈的使用】

给定一个只包含(){}[]的字符串s。判断字符串是否有效:
左右相同类型的括号要闭合。【必须以正确的顺序闭合】

正确顺序闭合?——> 内层的需要先闭合完、外层才能闭合!
——>很容易想到使用数据结构:栈!
先进后出。先出现的左符号(即外层的)、要放在栈底、等后出现的左符号(内层)闭合完!
再轮到栈底(外层的符号)弹出,然后判断是否闭合!——即:按顺序闭合

    public static boolean test(String s){// 排除特殊情况if (s.length() == 0 || s.length() % 2 != 0){System.out.println("s的长度为0、或长度不为偶数:返回false");return false;}// 用哈希表存储字符的映射规则Map<Character,Character> map = new HashMap();map.put('[',']');map.put('{','}');map.put('(',')');// 定义一个栈Stack<Character> stack = new Stack();// 遍历字符串for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);1、如果碰到右闭合符!则不压入、pop出栈顶、判断是否闭合// 相当于数据结构算法中学过的:碰到运算符+-*/()等优先级高的字符,则出栈。if (c == ']' || c == '}' || c == ')'){// 如果第一个字符就为右闭合符:则此时stack中还未push。为null。返回falseif(stack == null){return false;}// 如果pop出的左闭合符和对应的右闭合符不匹配。则返回falseif (map.get(stack.pop()) != c){return false;}}else {2、否则:是左闭合符:入栈stack.push(c);}}return true;}

21、合并两个有序链表【递归】***

将两个有序链表(非递减链表)合并为一个升序链表
【方法一】:递归:在原链表上合并。不新增链表。
递归要素:终止条件:l1或 l2为空时。
递归的理解:不用一步步去想中间的递归步骤。会绕晕的!
我们只看递归的最后一步:即递归出口的地方。本题中的倒数第二轮递归判断出:
(Node:4.val < Node:5.val):因此走:head2.next = mergeList(head1, head2.next);
head2:节点4、head2.next:节点4的next指针:即【连接线】
将head2.next(即null)作为最后一轮递归方法mergeList中的参数2传进去(ListNode head2)
因为head2.next 为 null。即最后一轮递归中。判断出head2 == null。
此处:递归结束!return head1;。而head1即为节点5。return节点5到上一轮递归方法的结果中————>①
于是上一轮递归方法head2.next = mergeList(head1, head2.next);就化为:【head2.next = 节点5】
即图中红色箭头的那根线:节点4指向节点5。
由于找到了出口。代码head2.next = mergeList(head1, head2.next); 执行完毕。
就执行下一行:return head2; ———————————————————————————————>②
而head2不就是本次倒数第二轮递归的节点4吗?最后一轮递归找到了出口:return节点5(①)。
并组成了合并后的有序链表:4 -> 5。节点4即为排好序的链表的头结点!
因此这里return head2(②); 即返回了排好序的链表的头结点:4
关键在于:从递归的最后一步(出口处)开始分析。只有最后一步找到出口return后,每一轮的递归方法才会执行完毕得到返回值,从而每轮的递归方法才能结束。如果找不到出口。就会一直递归下去。

    public static ListNode mergeList(ListNode head1, ListNode head2){if (head1 == null){return head2;}if (head2 == null){return head1;}if (head1.val < head2.val){// head1.next即为head1的连接线!// 因为head1是本轮中,较小的值。因此他就作为本轮的头结点。// 即:【本轮较小的节点head1作为头结点:指向已合并好的链表的头】// 一直递归下去:mergeList递归出口就是l1或l2为null时。// mergeList即合并好的有序链表。每次递归都让本次较小的节点与排好序的链表头相连!head1.next = mergeList(head1.next, head2);return head1;}else {head2.next = mergeList(head1, head2.next);return head2;}}static class ListNode{private int val;private ListNode next;public ListNode(){}public ListNode(int value){this.val = value;}}

方法二:不用递归。用新的一个链表来存储结果(也就是第23题:合并K个升序链表所使用的方法)

 public static ListNode mergeList(ListNode head1, ListNode head2){if (head1 == null || head2 == null){return head1 == null ? head2 : head1;}// 新链表的 哑元节点ListNode head = new ListNode(0);// 指针ListNode pointer = head, first = head1, second = head2;while (first != null && second != null){if (first.val < second.val){pointer.next = first;first = first.next;}else {pointer.next = second;second = second.next;}pointer = pointer.next;}pointer.next = (first == null ? second : first);return head.next;}

22、括号生成【深度优先遍历 + 回溯递归】

这题有点类似17题电话号码的递归实现
根节点为 ( 时。子节点可能为 ( 或 )、依次递归寻找合法的子节点所构成的树
设n为()括号的数量。根据给定的n。返回出所有合法的括号组合


private static List<String> result = new ArrayList<>();public static void main(String[] args) {test(2).forEach(System.out::println);}public static List<String> test(int n){// 排除特殊情况if (n == 0){return null;}//调用递归方法dfs("", 0, 0, n);return result;}// left right分别表示已使用的“(” 和 “)”的数量。  current表示本递归分支中括号的组成结果private static void dfs(String current, int left, int right, int n){// 递归出口:left和right的使用数都达到n,则结束if (left == n && right == n){result.add(current);return;}// 如果当前left<right!说明本分支的组合是不合法的,应该去除//  即:将本二叉树的分支剪掉if (left < right){return;}// 如果 ( 还有可用数、则加入一个 (if (left < n){dfs(current + "(", left + 1, right, n);}if (right < n){dfs(current + ")", left, right + 1, n);}}

n=2时——> 结果:


24、两两交换链表中的节点【回溯递归】

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表!
注意:要确实去交换节点。而不是交换节点的值。

    public static void main(String[] args) {ListNode list1 = new ListNode(1);list1.next = new ListNode(2);list1.next.next = new ListNode(3);list1.next.next.next = new ListNode(4);StringBuilder sb = new StringBuilder();sb.append("head");ListNode listNode = exchangeTwoNode(list1);// 输出结果while (listNode != null) {sb.append( "->" + listNode.val);listNode = listNode.next;}System.out.println(sb);}【交换方法】private static ListNode exchangeTwoNode(ListNode head){// 当前无节点,或只有一个节点时:无法交换,直接原样返回if (head == null || head.next == null){return head;}// head的next就是交换后的新head(先把旧头结点摘下来)ListNode newHead = head.next;// 下一轮要交换的链表是newHead.next ==>// 即、把:head.next.next(旧的尾节点所指向的那个节点:即为下一轮要交换的链表的链表头)// 作为下一轮要交换的链表头,传入递归方法。// 让head指向 -> 交换后的链表。head.next = exchangeTwoNode(newHead.next);// 新的头结点的 指针 -> 指向尾节点(旧头)newHead.next = head;// 返回新头结点return newHead;}static class ListNode{private int val;private ListNode next;public ListNode(int value){this.val = value;}}

29、两数相除【移位运算】

给定两个整数,被除数dividend和除数divisor。将两数相除,要求不使用乘法、除法和mod运算符。
返回被除数dividend除以除数divisor得到的商。
整数触发的结果应当截取其(truncate)小数部分。例如truncate(8.345) = 8
这题LeetCode用了移位运算来实现➗2。以下是我的解法,移位运算不想了解她了

    private static int truncate(int dividend, int divisor){long result = 0;int last = 0;// 默认为正数boolean flag = true;if ((dividend < 0 && divisor > 0) || (dividend > 0 && divisor < 0)){flag = false;}dividend = Math.abs(dividend);divisor = Math.abs(divisor);while (last + divisor< dividend) {last += divisor;result++;}if (result >= Integer.MAX_VALUE){if (flag){return Integer.MAX_VALUE;}else {return Integer.MIN_VALUE;}}return flag ? (int)result : (int)-result;}

31、下一个排列

实现获取【下一个排列】的函数、算法需要将给定的数字序列重新排列成字典序中下一个更大的排列。
——>即:组合出下一个更大的整数
如果不存在下一个更大的排列,则将数组重新排列成最小的排列(即升序排列)
必须【原地】修改,允许使用额外的常数空间
eg:输入nums = [1, ,2, 3] 输出:[1, 3, 2]
关键在于:对【下一个排列】的理解

    public static void main(String[] args) {int[] param = {1,2,3,8,5,7,6,4};int[] result = nextSorted(param);for (int i : result) {System.out.print(i + " ");}}public static int[] nextSorted(int[] nums){// 特殊校验if (nums.length <= 1){return null;}// minIndex记录着需要交换的值往右的数组中:比它大的最小的数//eg:图中需要交换的值是:5。5往右的数组中,6和7都比他大、但是6是比他大的最小的数// 因此6所在的索引就存入minIndex、将来6就会和5进行交换int minIndex = -1;// 是否找到满足条件的值boolean flag = false;// 从后往前遍历:nums[i]为当前需要比较的值for (int i = nums.length - 2; i > 0; i--) {// 遍历i 往右的数组for (int j = i + 1; j < nums.length - 1; j++) {// 如果找到了比 nums[i]大的数、if (nums[j] > nums[i]){// 比较本轮数组中找出的比他大的数 是否 < 上一轮找出的比他大的数?// 是:把本轮找出的数的索引存入、  否:保持原样// 这里需要判断minIndex==-1?  即:是否是第一次找到比nums[i]大的数minIndex = Math.min(nums[j], minIndex == -1 ? nums[j] : nums[minIndex]) == nums[j] ? j : minIndex;// 条件已满足!flag = true;}}// 如果条件满足,交换值if (flag){// 交换值int temp = nums[i];nums[i] = nums[minIndex];nums[minIndex] = temp;// 为本轮i往右的数组进行排序--> // 注意 这里sort(a,fromIndex,toIndex):这个to需要实际的toIndex + 1// 因为源码实际调用时 toIndex-1 了:DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);Arrays.sort(nums,i + 1, nums.length);// returnreturn nums;}}// 如果找不到下一个排列、则将数组重新以升序排序Arrays.sort(nums);return null;}

33、搜索旋转排序数组

一个元素互不相同的整数递增的数组nums、在传递给函数前会在随机一个下标k处进行旋转、
然后再传递给函数、请你在传递后的数组中、找出给定的目标值target。
解题关键点:有序数组的查询、必然是用二分查找、那么这个数组被旋转过、仍然可用二分查找吗?
当然是可以的!思路在于:每次确定二分查找的mid时、左边的数组和右边的数组总有一个是有序数组
通过左边或右边的有序数组和target的比较、就能确定下次二分查找该往哪边进行
eg:nums = {0,1,2,4,5,6,7}。在下标k=3处旋转后,nums = {4,5,6,7,0,1,2}

    private static int searchSpinSortedArray(int[] nums, int target){int len = nums.length;if (len == 0){return -1;}int left = 0, right = len - 1;while (left <= right){int mid = (right - left) / 2;if (nums[mid] == target){return mid;}// 接下来判断下一轮二分查找该往mid的左边找、还是右边找//如果mid的左边是有序数组if (nums[0] <= nums[mid]){// 并且target的值在mid左边的有序数组内if (nums[0] <= target && target < nums[mid]){right = mid - 1;}else {left = mid + 1;}}else// 如果0>mid的值:说明mid的右边才是有序数组{// 并且target值在mid右边的有序数组范围内if (nums[mid] < target && target <= nums[left - 1]){left = mid + 1;}else {right = mid - 1;}}}return -1;}

34、在排序数组中找出目标元素第一个和最后一个出现的位置。

没啥好说的、一个简单的二分查找法。

public static void main(String[] args) {int[] param = {5,7,7,8,8,10};for (int i : getFirstAndLast(param, 8)) {System.out.print(i + " ");}}private static int[] getFirstAndLast(int[] nums, int target){int[] result = {-1,-1};int len = nums.length;if (len == 0 || target < nums[0]){return result;}int left = 0, right = len -1;for (int i = 0; i < len; i++) {int mid = (right + left) / 2;if (target == nums[mid]){int temp = mid;result[0] = mid;result[1] = mid;while ((mid - 1) >= 0 && nums[mid] == nums[mid - 1]){result[0] = mid - 1;mid--;}while ((temp + 1) < len && nums[temp] == nums[temp + 1]){result[1] = temp + 1;mid++;}break;}if (target <= nums[mid]){right = mid - 1;}if (target > nums[mid]){left = mid + 1;}}return result;}

36、有效数独

判断输入的9x9数独是否有效:(没有数字的格子用" . "表示)
1、同一行1-9数字只能出现一次
2、同一列1-9数字只能出现一次
3、每个3X3方格中、1-9数字只能出现一次
类似第6题:Z字形变换。这种图形化的参数传递进来、都需要用某种数据结构来表示他。
这里用的是矩阵来表示

private static boolean judgeEffective(char[][] board) {//每一行1-9数字是否出现int[][] row = new int[9][10];//每一列1-9数字是否出现int[][] col = new int[9][10];//每一个方格中1-9数字是否出现int[][] box = new int[9][10];for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {int current = board[i][j] == '.' ? -1 : board[i][j] - '0';// 如果当前格子没有数字、则继续下一次循环。if (current == -1) {continue;}//判断在当前行是否出现过if (row[i][current] != 0) {return false;}//判断在当前列是否出现过if (col[j][current] != 0) {return false;}//判断在当前方格是否出现过if (box[i + 1 + j / 3][current] != 0) {return false;}// 都没出现过、则本轮有效、出现次数+1row[i][current] = 1;col[j][current] = 1;box[i + 1 + j / 3][current] = 1;}}return true;}

38、外观数列【递归】

外观数列的第一项:n=1时、值是1
第二项:11 ——> 一个1
第三项:21 ——> 两个1
第四项:1211 ——> 一个2一个1
第五项:111221 ——> 一个1一个2两个1
每一项都是对前一项的描述:可以看成一种由递归公式组成的数列。
显然、这一题用递归解决

    public static void main(String[] args) {System.out.println(countAndSay(5));}private static String countAndSay(int n){// 递归出口:n=1时、值为1if (n == 1){return "1";}//递归拿到n的前一项的值:对前一项的描述就是本项的结果、本项的结果又返回给下一项String lastResult = countAndSay(n - 1);int first = 0;List<String> temp = new ArrayList<>();// 将前一项按相同连续字符划分成多个字符串组for (int i = 0; i < lastResult.length(); i++) {if (i + 1 == lastResult.length()){temp.add(lastResult.substring(first));break;}if (lastResult.charAt(first) == lastResult.charAt(i + 1)){continue;}temp.add(lastResult.substring(first, i + 1));first = i + 1;}String result = "";//对划分出的字符串组进行描述:几个几? length()个charAt(0)for (int i = 0; i < temp.size(); i++) {result += temp.get(i).length() + String.valueOf(temp.get(i).charAt(0));}return result;}

300、最长递增子序列【动态规划】

给你一个不重复的数组、求该数组的最长递增子序列的长度
递增子序列:严格按照递增的顺序排列

方法一(动态规划):

    public static void main(String[] args) {int[] param = new int[8];param[0] = 2;param[1] = 5;param[2] = 7;param[3] = 8;param[4] = 6;param[5] = 9;param[6] = 10;param[7] = 18;System.out.println(lengthOfLIS(param));}public static int lengthOfLIS(int[] nums) {if(nums.length == 0) return 0;// dp[i]存的是nums数组中、以索引i为结尾的最长递增子序列的 长度!// 此dp[i]存的不是当前找到的最长递增子序列、不是严格递增的。因此不能二分int[] dp = new int[nums.length];int res = 0;Arrays.fill(dp, 1);for(int i = 0; i < nums.length; i++) {// 与i之前的各个值nums[j]进行比较for(int j = 0; j < i; j++) {// 如果nums[i]比值nums[j]大、// 则下标为i时的最长子序列长度、就可以在索引为j时的长度基础上 + 1if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);}res = Math.max(res, dp[i]);}return res;}

方法二(动态规划 + 二分查找)(时间复杂度 O(n·logn)):

    public static int lengthOfLIS(int[] nums){// 此dp存储:当前找到的最长递增子序列int[] dp = new int[nums.length];dp[0] = nums[0];int x = 0;for (int i = 1; i < nums.length; i++) {// 如果num比dp[x]小(如果num比之前的递增子序列的最大值更小)if (nums[i] < dp[x]){// 如果nums[i]比dp[x]小 且比dp[x]里其他的数大(满足递增条件)、才能覆盖// 由于dp[x]存的是严格递增的子序列、因此可以用二分法int left = 0;while (left < x){int m = (x + left) / 2;//若 中间数仍小于num:下一次在右半区间比较if (dp[m] < nums[i]){left = m + 1;}else {// 否则、说明不能覆盖(覆盖后就不符合严格递增了)left = x;}}// 如果flag=true 覆盖。if (left < x){dp[x] = nums[i];}}else {dp[++x] = nums[i];}}return x+1;}

==============================================================================

高级算法

4、寻找两个正序数组的中位数【归并排序、二分查找】

此题目的:并不是遍历所有!而是找到某个数 (中位数)
解法一:由于是两个正序数组中寻找:考虑是否使用【归并】化为一个数组?
解法二:寻找【顺序数组中】的某个数?考虑使用二分查找减少查找次数(每次排除一半!)

给定两个大小分别为m和n的正序数组nums1和nums2。请你找出并返回这两个正序数组的中位数、
示例:nums1 = [1,3] nums2 = [2] 输出:2
示例:nums1 = [1,2] nums2 = [3,4] 输出:2.5
我的结论是找规律:nums1求和 = sum1 nums2求和 = sum2 中位数:sum1 + sum2 / 2

显然,我的这种做法虽然能做出来。但是没有用到所谓的归并排序算法,时间O(m + n)。

    public static double getMidNum(int[] nums1, int[] nums2) {int m = nums1.length;int n = nums2.length;double sum1, sum2;int midm = m / 2, midn = n / 2;if (m == 0) {sum1 = 0;} else if (m == 1) {sum1 = nums1[0];} else {if (m % 2 == 0) {sum1 = (nums1[midm - 1] + nums1[midm]) / 2;} else {sum1 = nums1[midm] / 2;}}if (n == 0) {sum2 = 0;} else if (n == 1) {sum2 = nums2[0];} else {if (n % 2 == 0) {sum2 = (nums2[midn - 1] + nums2[midn]) / 2;} else {sum2 = nums2[midn] / 2;}}if (m == 0) {return sum2;} else if (n == 0) {return sum1;} else {return (sum1 + sum2) / 2;}}

来看看【归并排序】算法的解题步骤:时间O(m + n) 空间O(m + n)

int[] nums;int m = nums1.length;int n = nums2.length;nums = new int[m + n];// 如果nums1为空,则可以直接返回nums2的中位数if (m == 0) {if (n % 2 == 0) {return (nums2[n / 2 - 1] + nums2[n / 2]) / 2.0;} else {return nums2[n / 2];}}// 如果nums2为空,则可以直接返回nums1的中位数if (n == 0) {if (m % 2 == 0) {return (nums1[m / 2 - 1] + nums1[m / 2]) / 2.0;} else {return nums1[m / 2];}}
// 当两个数组都不为空时,通过while循环合并两个数组int count = 0;int i = 0, j = 0;while (count != (m + n)) {// 如果nums1的指针走完了,则把nums2剩余的数全部加进来if (i == m) {while (j != n) {nums[count++] = nums2[j++];}break;}// 如果nums2的指针走完了,则把nums1剩余的数全部加进来if (j == n) {while (i != m) {nums[count++] = nums1[i++];}break;}
// 把目前两个数组的i j指针对应的值中,较小的值放入归并后的数组中!if (nums1[i] < nums2[j]) {nums[count++] = nums1[i++];} else {nums[count++] = nums2[j++];}}
// 最后输出 合并后的数组的 中位数if (count % 2 == 0) {return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;} else {return nums[count / 2];}

【二分查找】 时间复杂度达到了极致!:O(log(m+n)) 空间复杂度O(1)
根据中位数的定义:【m+n为奇数】时:中位数为两个数组中【第(m+n)/2 + 1个】元素。
【m+n为偶数】时,中位数为两个数组中【第(m+n)/2个】元素 he 【第(m+n)/2 + 1个】元素的平均值
因此—> 题目转换为:寻找两个有序数组中:第k小的数:与m+n的奇偶性相关

   public double findMedianSortedArrays(int[] nums1, int[] nums2) {int n = nums1.length;int m = nums2.length;
// 如果n+m是偶数,则中位数为第left和right的平均值int left = (n + m + 1) / 2;int right = (n + m + 2) / 2;//将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
}private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {// eg: 6 = 5 - 0 + 1;int len1 = end1 - start1 + 1;int len2 = end2 - start2 + 1;//让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1 ———调整参数顺序if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);if (len1 == 0) return nums2[start2 + k - 1];
//出口:当k被化为求的是第1小的数,就可以返回了!if (k == 1) return Math.min(nums1[start1], nums2[start2]);
// 为防止索引越界:
//如果k/2比数组长度还大,那最多只能取数组长度那么长
// i j 为本次要排除掉的第x个数对应的数组下标int i = start1 + Math.min(len1, k / 2) - 1;int j = start2 + Math.min(len2, k / 2) - 1;
// 由于是有序递增数组,所以可以直接排除较小的if (nums1[i] > nums2[j]) {return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));}else {return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));}}

23、合并K个升序链表【合并2个有序链表(方法二)升级版】

给你一个【链表数组】,每个链表都是升序排序。请你将所有链表合并为一个升序链表,并返回。
eg:lists = [[1,4,5], [1,3,4], [2,6]]。输出:[1,1,2,3,4,4,5,6]
合并K个升序链表——>就是合并2个有序链表(第21题)的升级版
21题用得递归。但这里没有用递归哈。

private static ListNode result = null;public static void main(String[] args) {ListNode list1 = new ListNode(1);list1.next = new ListNode(4);list1.next.next = new ListNode(5);ListNode list2 = new ListNode(1);list2.next = new ListNode(3);list2.next.next = new ListNode(4);ListNode list3 = new ListNode(2);list3.next = new ListNode(6);ListNode[] parm = {list1,list2,list3};// 参数组装完毕StringBuilder sb = new StringBuilder();sb.append("head");ListNode listNode = mergeKListNode(parm);// 输出结果while (listNode != null) {sb.append( "->" + listNode.val);listNode = listNode.next;}System.out.println(sb);}private static ListNode mergeKListNode(ListNode[] lists){// 特判if (lists.length == 0){return null;}int k = lists.length;// 每次取链表数组的两个链表元素来合并为一个新的链表。// 合并后的结果链表。又作为参数和下一个链表元素合并for (int i = 0; i < k; i++) {// head.next为2个链表合并后的第一个节点// 赋给result。即:result = head.next; 即result为新链表的头结点。// 即:result就代表着新的链表result = mergeTwoListNode(result ,lists[i]);}return result;}private static ListNode mergeTwoListNode(ListNode list1, ListNode list2){if (list1 == null || list2 == null){return list1 == null ? list2 : list1;}// 因为要构造一个新的链表。首先要先创建一个哑元节点head。指向该链表的第一个节点。ListNode head = new ListNode(0);// 声明一个指针——>使得head.next永远指向这个链表的头结点。
//(复制一个head节点作为指针:这是必要的!如果直接拿head当指针去移动。则最后返回时,head就会在尾巴,找不到链表头了)ListNode pointer = head, first = list1, second = list2;// 当本轮要比较的节点:list1和List2都不为null时。把小的值加入结果while (first != null && second != null){if (first.val < second.val){// 小的节点添加进结果pointer.next = first;// 指针后移first = first.next;}else {pointer.next = second;second = second.next;}pointer = pointer.next;}// 当其中某个链表被拼接完了,则另一个链表直接接上即可pointer.next = (first != null ? first : second);// head.next为2个链表合并后的首节点(代表该链表)。而head是new出来的一个dummy节点return head.next;}static class ListNode{private int val;private ListNode next;public ListNode(int value){this.val = value;}}

25、翻转K个一组的链表

给你一个链表:以K个节点为一组,对其进行反转。并返回反转后的链表。
如果节点数不足K,则保持原样
反转链表图解
关键步骤:
1、设一个dummy节点:作为
2、用临时变量暂存下一次要反转的链表头:即 next = head.next;
3、执行反转操作:head.next = pre;

 public static void main(String[] args) {ListNode list1 = new ListNode(1);list1.next = new ListNode(2);list1.next.next = new ListNode(3);list1.next.next.next = new ListNode(4);list1.next.next.next.next = new ListNode(5);StringBuilder sb = new StringBuilder();sb.append("head");ListNode listNode = reverseKGroup(list1, 3);// 输出结果while (listNode != null) {sb.append( "->" + listNode.val);listNode = listNode.next;}System.out.println(sb);}public static ListNode reverseKGroup(ListNode head, int k) {if (head == null || head.next == null) {return head;}// tail节点就是下一轮要翻转的链表的头结点。ListNode tail = head;for (int i = 0; i < k; i++) {//剩余数量小于k的话,则不需要反转。if (tail == null) {return head;}tail = tail.next;}// 反转前 k 个元素ListNode newHead = reverse(head, tail);// 本轮翻转结束:递归:让本组的旧头head指向下一组的新头newHead//reverseKGroup(tail, k);返回的是:newHead。// 即:让head(旧头/新尾)指向newHeadhead.next = reverseKGroup(tail, k);return newHead;}/*左闭右开区间*/private static ListNode reverse(ListNode head, ListNode tail) {// 前一节点ListNode newHead = null;ListNode next = null;while (head != tail) {// 先用temp变量暂存head.next节点(摘下 head的next指针)// 拿到头结点的下一个节点:next节点next = head.next;【关键步骤												

算法笔记——每日一题(完结)相关推荐

  1. codeup墓地目录(算法笔记习题刷题笔记)

    在线codeup contest 地址:http://codeup.cn/contest.php Contest100000575 - <算法笔记>3.1小节--入门模拟->简单模拟 ...

  2. 【LeetCode笔记 - 每日一题】375. 猜数字游戏 II (Java、DFS、动态规划)

    文章目录 题目描述 思路 && 代码 DFS 动态规划 新系列-用于区分开高频题和每日一题- 题目描述 一眼二分,但是实际上并不是 这题让我想到社团的猜数字游戏-但是给钱是真过分了= ...

  3. 【LeetCode笔记 - 每日一题】384. 打乱数组(Java、洗牌算法)

    文章目录 题目描述 思路 && 代码 题目描述 中等题,很赞!第一次碰到涉及洗牌算法的题 有点涉及概率,主要是要实现公平的随机 思路 && 代码 采用了 Knuth 洗 ...

  4. LeetCode算法,每日一题,冲击阿里巴巴,day7

    目录 1.LeetCode 257.二叉树的所有路径 题目 思路与算法 小编菜解 2.LeetCode 258.各位相加 题目 小编解题思路 小编菜解 思路与算法 大佬指点江山 3.LeetCode ...

  5. LeetCode算法,每日一题,冲击字节跳动

    目录 1.LeetCode 20.有效的括号 题目 小编菜解 思路及算法 大神解法 2.LeetCode 26.删除有序数组中的重复项 题目 小编菜解初版 小编菜解改进版 思路及算法 大神解法 3.L ...

  6. 算法强化每日一题--组队竞赛

    大家好 先看看题目 链接:组队竞赛__牛客网 [编程题]组队竞赛 牛牛举办了一次编程比赛,参加比赛的有3*n个选手,每个选手都有一个水平值a_i.现在要将这些选手进行组队,一共组成n个队伍,即每个队伍 ...

  7. 【LeetCode笔记 - 每日一题】373. 查找和最小的 K 对数字(Java、堆、优先队列)

    文章目录 题目描述 思路 && 代码 题目描述 几天没打题,感觉脑子都是一团浆糊.... 升序:肯定得用这条件来优化复杂度 数对:用 int[2] 来表示 思路 && ...

  8. 【LeetCode笔记 - 每日一题】423. 从英文中重建数字(Java、字符串、偏思路)

    文章目录 题目描述 思路 && 代码 题目描述 看了题目以后想到啥? 字符数量统计 银行家算法逐个拆解 建立数字 - 字符串的全局映射 思路 && 代码 抄答案了,采取 ...

  9. [算法]LeetCode每日一题--174. 地下城游戏(Java)

    DailyChallenge 174. 地下城游戏 Hard20200712 Description 一些恶魔抓住了公主(P)并将她关在了地下城的右下角.地下城是由 M x N 个房间组成的二维网格. ...

最新文章

  1. (一) 自带刷新的列表-LtRecyclerView v2.x版本(LtAdapter)(基本使用)
  2. 图解:从单个服务器扩展到百万用户的系统
  3. mysql5.5索引,MySQL--5索引选择原则
  4. Jquery事件、冒泡、委托与节点
  5. POD 创建 Xcode 项目组
  6. 获取编译学习笔记 (十一年)—— 内的中间
  7. 基于Java的实现宠物管理系统的设计与实现
  8. 英雄联盟游戏结束后显示与服务器失去连接,英雄联盟游戏被终止连接不上解决方法...
  9. 微信 0day漏洞复现
  10. python14张图下载_Python网络爬虫入门(三)—— 做个简陋的pixabay 图片下载器 (附源码)...
  11. matlab 调整灰度,matlab灰度变换函数
  12. VMware 配置局域网内访问
  13. Cell子刊:成年同卵双胞胎的病毒组多样性与肠道微生物组多样性相关
  14. Python GUI编程—Tkinter实战一(生日快乐小程序)
  15. 使用正则表达式将数值转化为千分位格式
  16. 什么是云原生?这回终于有人讲明白了
  17. C语言学生成绩管理系统——检查学号姓名,双向循环链表
  18. 计算机网络智能小区综合分布线系统的总结,智能住宅小区综合布线系统的构建...
  19. Linux - 常见端口和服务的对照和解释
  20. Chrome 用户数据配置文件夹保存路径在哪?(Mac OS X/Windows/Linux)

热门文章

  1. 最短路径树(SPT)介绍及matlab代码
  2. 非常恶俗地分享一首歌曲(董贞·逍遥游)
  3. C语言 模板化<template>编程萌芽
  4. Wannafly挑战赛13 - B Jxc的军训
  5. 无线ap升级胖AP 和 学习笔记
  6. nms之——流式服务器(直播推拉流等)
  7. sql语句优化(mysql)
  8. 【研究生开学季】让你幸福感砰砰砰的宿舍神器
  9. Bespin Global荣膺“Gartner2020全球公有云基础设施管理服务提供商魔力象限领导者”
  10. unity实现小球poke不穿膜