数组中重复的数 题目:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/submissions/
首先肯定不能暴力(不想动脑,结果直接死了),直接超时,相对简单点是先排序,然后检查前后两个数是否相同。
方法1:排序+遍历判断
1 2 3 4 5 6 7 8 9 class Solution { public int findRepeatNumber (int [] nums) { Arrays.sort(nums); for (int i=1 ;i<nums.length;i++) if (nums[i-1 ]==nums[i]) return nums[i]; return -1 ; } }
方法2:利用Set无重复特性
1 2 3 4 5 6 7 8 class Solution { public int findRepeatNumber (int [] nums) { Set<Integer> set = new HashSet<>(); for (int num : nums) if (!set.add(num)) return num; return -1 ; } }
方法3:原地交换,书上的解法,可以说是最优解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int findRepeatNumber (int [] nums) { int i=0 ; while (i<nums.length){ if (nums[i]==i){ i++; continue ; } if (nums[i]==nums[nums[i]]) return nums[i]; nums[i] ^= nums[nums[i]]; nums[nums[i]] ^= nums[i]; nums[i] ^= nums[nums[i]]; } return -1 ; } }
二维数组中的查找 题目:https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/
暴力不考虑啊,看了下书上提示就懂了大概方法,但一些特殊输入就没考虑到,故报错了好几次。本题从右上角和左下角开始查找都是OK的,因为有一大一小的两个相邻值可以判断位置,缩小查找范围。而使用左上或右下,则会全大或全小,查找不会缩小范围,无法解决问题。
方法1:右上角开始查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public boolean findNumberIn2DArray (int [][] matrix, int target) { if (matrix.length==0 ) return false ; int n=0 ,m=matrix[0 ].length-1 ; while (n<matrix.length && m>=0 ){ if (matrix[n][m] > target) m--; else if (matrix[n][m] < target) n++; else return true ; } return false ; } }
方法2:左下角开始查找
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public boolean findNumberIn2DArray (int [][] matrix, int target) { int n=matrix.length-1 ,m=0 ; while (n>=0 && m<matrix[0 ].length){ if (matrix[n][m] == target) return true ; if (matrix[n][m] > target) n--; else if (matrix[n][m] < target) m++; else return true ; } return false ; } }
替换空格 题目:https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/
比较简单啊,我们遍历一遍String转化后的数组,每次遇到字符=’ ‘就往StringBuilder添加”%20”并跳出,其他情况直接添加字符。最后转String输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public String replaceSpace (String s) { StringBuilder sb = new StringBuilder(); for (char c : s.toCharArray()){ if (c == ' ' ) { sb.append("%20" ); continue ; } sb.append(c); } return sb.toString(); } }
从尾到头打印链表 题目:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/
这里用栈就很好想,先进后出吗,先压入栈,在打印栈就是倒叙输出了,用数组也一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int [] reversePrint(ListNode head) { Stack<Integer> s = new Stack<Integer>(); while (head!=null ){ s.push(head.val); head=head.next; } int [] arr = new int [s.size()]; for (int i=0 ;i<arr.length;i++) arr[i] = s.pop(); return arr; } }
重建二叉树 题目:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
前序和中序结合推树很好想,但递归不好写,注意右子树的根结点位置是怎么生成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { int [] preorder; Map<Integer,Integer> map =new HashMap<>(); public TreeNode buildTree (int [] preorder, int [] inorder) { this .preorder = preorder; for (int i=0 ;i<inorder.length;i++) map.put(inorder[i],i); return res(0 ,0 ,inorder.length-1 ); } TreeNode res (int root,int left,int right) { if (left>right) return null ; TreeNode node = new TreeNode(preorder[root]); int i = map.get(preorder[root]); node.left = res(root+1 ,left,i-1 ); node.right = res(root + i-left + 1 ,i+1 ,right); return node; } }
用两个栈实现队列 题目:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/
A栈主栈,B栈辅助栈,入队时直接压入A栈,出队时A出B入,由于两次先进后出,此时B出栈便会实现元素先进先出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class CQueue { Stack<Integer> a,b; public CQueue () { a = new Stack<Integer>(); b = new Stack<Integer>(); } public void appendTail (int value) { a.push(value); } public int deleteHead () { if (!b.empty()) return b.pop(); if (a.empty()) return -1 ; while (!a.empty()) b.push(a.pop()); return b.pop(); } }
斐波那契数列 题目:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/、
递归从上到下,会多次重复计算,效率低。
这里使用循环,每次将fn=f0+f1,并更新f0和f1,注意int上限是2^31,一般运算会越界,所以使用取余计算fn=(f0+f1)%1000000007
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public int fib (int n) { if (n==0 ) return 0 ; if (n==1 ) return 1 ; int f0=0 ,f1=1 ,fn=0 ; for (int i=2 ;i<=n;i++){ fn=(f0+f1)%1000000007 ; f0 = f1; f1=fn; } return fn; } }
青蛙跳台(斐波那契变种) 题目:https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/
这题首先要通过题目联想到跳上第n层只有上1/2层两种方法,推出f(n)=f(n-1)+f(n-2),然后得到f(2)=f(1)+f(0)并结合事实推出f(0)=1,这里便是和斐波那契最大的区别,其他的就一样了,当然不能使用递归,毕竟重复计算太多了会超时,使用循环O(n)遍历即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int numWays (int n) { if (n==0 ||n==1 ) return 1 ; int f0=1 ,f1=1 ,fn=0 ; for (int i=2 ;i<=n;i++){ fn = (f0 + f1)%1000000007 ; f0 = f1; f1 = fn; } return fn; } }
旋转数组的最小数字 题目:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/
遍历就不说了啊,这题目考的就是二分找旋转点,注释写的比较清除,还要注意,mid=j不能判断左右递增,因为[0,1,1,1,1,1]这种全是重复值的数组存在。还有为什么mid与j比较,而不是与i比较,因为有[1,2,3,4,5]这种数组,只有右递增,旋转点是num[0],这个问题要考虑清楚。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int minArray (int [] numbers) { int i=0 ,j=numbers.length-1 ; while (i<j){ int mid = (i+j)/2 ; if (numbers[mid]>numbers[j]) i = mid+1 ; else if (numbers[mid]<numbers[j]) j = mid; else j--; } return numbers[i]; } }
矩阵中的路径 题目:https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/
这题的思想好想,但写起来就不容易了,且要注意细节,就是回溯的时候要把改变的空字符重置回初值,这是回溯法的关键,我理解是当前错误的路径上标记过的值(空字符)全作废,因为这条路是错的,一直回到正确的岔路口来走另一条路。感觉这题没有吃透。。。
评论区看到的分析:一个比较难理解的点,递归搜索匹配字符串过程中,需要 board[i] [j] = ‘0/‘ 来防止 ”走回头路“ 。当匹配字符串不成功时,会回溯返回,此时需要board[i] [j] = word[k] 来”取消对此单元格的标记”。 在DFS过程中,每个单元格会多次被访问的, board[i] [j] = ‘0/‘只是要保证在当前匹配方案中不要走回头路,不复原,在下一个匹配方案中会影响最终结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public boolean exist (char [][] board, String word) { char [] words = word.toCharArray(); for (int i=0 ;i<board.length;i++) for (int j=0 ;j<board[0 ].length;j++) if (dfs(board,words,i,j,0 )) return true ; return false ; } boolean dfs (char [][] board,char [] words,int i,int j,int k) { if (i>=board.length || i<0 || j>=board[0 ].length || j<0 || board[i][j]!=words[k]) return false ; if (k==words.length-1 ) return true ; board[i][j] = '\0' ; boolean res = dfs(board,words,i+1 ,j,k+1 ) || dfs(board,words,i-1 ,j,k+1 ) || dfs(board,words,i,j+1 ,k+1 ) || dfs(board,words,i,j-1 ,k+1 ); board[i][j] = words[k]; return res; } }
注意其实回溯是针对错误路径的,对正确路径无影响,但为了方便,我们就不分情况回溯了。将代码中加上判断条件只在错误时回溯,发现结果仍然是对的,这也验证了我的想法。
1 2 if (res == false ) board[i][j] = words[k];
机器人的运动范围 题目:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/
深度优先遍历,每走一个新方格便记录该方格的数位和,并与判断条件比较,直到走到头往回退,给出结果。注意dfs返回值是其右和下的遍历情况+1,因为直接这一格可以走到,所以+1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { int m,n,k; boolean [][] visited; public int movingCount (int m, int n, int k) { this .m = m; this .n = n; this .k = k; this .visited = new boolean [m][n]; return dfs(0 ,0 ,0 ,0 ); } public int sum (int x) { int sum = 0 ; while (x != 0 ){ sum += x % 10 ; x /= 10 ; } return sum; } public int dfs (int i, int j, int sumi, int sumj) { if (i >= m || j >= n || k < sumi + sumj || visited[i][j]) return 0 ; visited[i][j] = true ; return 1 + dfs(i + 1 , j, sum(i + 1 ), sumj) + dfs(i, j + 1 , sumi, sum(j + 1 )); } }
剪绳子(n<=58,不考虑大数据) 题目:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/
最优子段的推导:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/mian-shi-ti-14-i-jian-sheng-zi-tan-xin-si-xiang-by/
这里使用贪心算法是最优解,而找出最优字段是关键,就是每个>5的数均可继续分割成2&3,2、3的搭配才是最小最优的乘积(整个具体分析得看大佬,例如5<2*3等等),而2&3的优先级怎么判断呢?
1 2 3 6=2+2+2=3+3,f(6)=2*2*2<3*3 所以3的优先级大于2 故我们使用3来作为倍数,剩下的分析看注释即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int cuttingRope (int n) { if (n<=3 ) return n-1 ; int i = n/3 ; if (n%3 == 0 ) return (int )Math.pow(3 ,i); if (n%3 == 1 ) return (int )Math.pow(3 ,i-1 )*4 ; return (int )Math.pow(3 ,i)*2 ; } }
剪绳子(大数据情况,考虑越界) 题目:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/
注意要逐位取余,不然连乘中途可能已经越界了,所以老办法不行,同一个思路要换一个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int cuttingRope (int n) { if (n<=3 ) return n-1 ; int i = n/3 ; if (n%3 == 0 ) return (int )Math.pow(3 ,i); if (n%3 == 1 ) return (int )Math.pow(3 ,i-1 )*4 ; return (int )Math.pow(3 ,i)*2 ; } }
二进制中1的个数 题目:https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/
1 2 3 4 5 6 7 8 ">>>"无符号右移 操作规则:无论正负数,前面补零。 ">>"右移 操作规则:正数前面补零,负数前面补1 "<<"左移 操作规则:无论正负数,后面补零。
末位按位与1 + 逐位右移:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Solution { public int hammingWeight (int n) { int res=0 ; while (n!=0 ){ res += n&1 ; n = n>>>1 ; } return res; } }
新思路:
数-1:即二进制最右的1变为0,该位后面的0全变为1
将-1的数&原数:可得到只有最右的1变为0的数,其余不变
重复该操作,直到全部变为0
1 2 3 4 5 6 7 8 9 10 public class Solution { public int hammingWeight (int n) { int res=0 ; while (n!=0 ){ res++; n = n & (n-1 ); } return res; } }
数值的整数次方 题目:https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/
参考:https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/solution/jian-zhi-offer-16-shu-zhi-de-zheng-shu-c-rgqy/
可类比斐波那契数列,均有递归关系,一般只在偶数情况下递归,奇数情况则提一个a,转变次数为偶数
1 2 3 4 5 6 7 8 偶数情况 a^n = a^(n/2) * a^(n/2) a^n = a^[2*(n/2)] a^n = (a^2)^(n/2) (效率更高,递归得用这个,不然超时) 奇数情况 a^n = a^[(n-1)/2] * a^[(n-1)/2] * a a^n = a * a^(2*[(n-1)/2]) a^n = a * (a^2)^[(n-1)/2]
迭代(以二进制来思考)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public double myPow (double x, int n) { if (x == 0 ) return 0 ; long m = n; double res = 1.0 ; if (m<0 ){ x = 1 /x; m = -m; } while (m>0 ){ if ((m&1 ) == 1 ) res *= x; x *= x; m >>= 1 ; } return res; } }
递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public double myPow (double x, int n) { if (x == 0 ) return 0 ; if (n==0 ){ return 1 ; }else if (n<0 ){ return 1 /(x * myPow(x,-n-1 )); }else if ((n&1 ) == 1 ){ return x * myPow(x,n-1 ); }else { return myPow(x*x,n/2 ); } } }
打印从1到最大的n位数(大数情况得重新看) 难,大数情况不好想
题目:https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/
参考:https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/solution/mian-shi-ti-17-da-yin-cong-1-dao-zui-da-de-n-wei-2/
int不越界
1 2 3 4 5 6 7 8 9 class Solution { public int [] printNumbers(int n) { int end = (int )Math.pow(10 ,n)-1 ; int [] res = new int [end]; for (int i=1 ;i<=end;i++) res[i-1 ]=i; return res; } }
大数情况!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { int [] res; int nine=0 ,count=0 ,start,n; char [] num,loop={'0' ,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' }; public int [] printNumbers(int n) { this .n = n; res = new int [(int )Math.pow(10 ,n)-1 ]; num = new char [n]; start = n-1 ; dfs(0 ); return res; } void dfs (int x) { if (x==n){ String s = String.valueOf(num).substring(start); if (!s.equals("0" )) res[count++] = Integer.parseInt(s); if (n-start == nine) start--; return ; } for (char i : loop){ if (i == '9' ) nine++; num[x] = i; dfs(x+1 ); } nine--; } }
删除链表的节点 题目:https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/
就是删除链表的节点,注意头结点为空的特殊情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public ListNode deleteNode (ListNode head, int val) { if (head == null ) return null ; if (head.val == val) return head.next; ListNode pre = head, cur = head.next; while (cur != null ){ if (cur.val == val){ pre.next = cur.next; break ; } pre = cur; cur = cur.next; } return head; } }
正则表达式匹配(难!) 题目:https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/
参考:https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/solution/jian-zhi-offer-19-zheng-ze-biao-da-shi-pi-pei-dong/
初始化时,要给两个字符串添加一个首空字符
正则表达式判断,匹配串当前字符是’ * ‘时
‘ * ‘修饰字符重复0次情况
‘ * ‘ 与上一个主串字符匹配,看当前主串字符是否仍能与 ‘ * ‘修饰字符匹配,即重复一次’ * ‘修饰字符
‘ * ‘ 与上一个主串字符匹配,但’ * ‘修饰’ . ‘,主串当前字符一定匹配
正则表达式判断,匹配串当前字符不是’ * ‘时,且前部分主串与匹配串相匹配
当前主串与匹配串字符相等
当前匹配串字符是’ . ‘,一定匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public boolean isMatch (String s, String p) { int m = s.length()+1 ,n = p.length()+1 ; boolean [][] dp = new boolean [m][n]; dp[0 ][0 ] = true ; for (int j=2 ;j<n;j += 2 ) dp[0 ][j] = dp[0 ][j-2 ] && p.charAt(j-1 ) == '*' ; for (int i=1 ;i<m;i++){ for (int j=1 ;j<n;j++){ if (p.charAt(j-1 ) == '*' ){ if (dp[i][j-2 ]) dp[i][j] = true ; else if (dp[i-1 ][j] && s.charAt(i-1 ) == p.charAt(j-2 )) dp[i][j] = true ; else if (dp[i-1 ][j] && p.charAt(j-2 ) == '.' ) dp[i][j] = true ; } else { if (dp[i-1 ][j-1 ] && s.charAt(i-1 ) == p.charAt(j-1 )) dp[i][j] = true ; else if (dp[i-1 ][j-1 ] && p.charAt(j-1 ) == '.' ) dp[i][j] = true ; } } } return dp[m-1 ][n-1 ]; } }
表示数值的字符串(优化了不用状态机的作法) 题目:https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/
参考:https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/solution/mian-shi-ti-20-biao-shi-shu-zhi-de-zi-fu-chuan-y-2/
k神的解法太妙了,看懂后就觉得理解起来很容易,用Map数组规定8种状态,然后从初值0开始,依当前情况进入对应的状态。
8种状态的个人理解我也写在注解里了,原谅我是个菜鸡,看半天才看懂,还没法举一反三😭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution { public boolean isNumber (String s) { Map[] states = { new HashMap<>() {{ put(' ' , 0 ); put('s' , 1 ); put('d' , 2 ); put('.' , 4 ); }}, new HashMap<>() {{ put('d' , 2 ); put('.' , 4 ); }}, new HashMap<>() {{ put('d' , 2 ); put('.' , 3 ); put('e' , 5 ); put(' ' , 8 ); }}, new HashMap<>() {{ put('d' , 3 ); put('e' , 5 ); put(' ' , 8 ); }}, new HashMap<>() {{ put('d' , 3 ); }}, new HashMap<>() {{ put('s' , 6 ); put('d' , 7 ); }}, new HashMap<>() {{ put('d' , 7 ); }}, new HashMap<>() {{ put('d' , 7 ); put(' ' , 8 ); }}, new HashMap<>() {{ put(' ' , 8 ); }} }; int p = 0 ; char t; for (char c : s.toCharArray()) { if (c >= '0' && c <= '9' ) t = 'd' ; else if (c == '+' || c == '-' ) t = 's' ; else if (c == 'e' || c == 'E' ) t = 'e' ; else if (c == '.' || c == ' ' ) t = c; else t = '?' ; if (!states[p].containsKey(t)) return false ; p = (int )states[p].get(t); } return p == 2 || p == 3 || p == 7 || p == 8 ; } }
2021.12.24 二刷剑指,yysy状态机一般人写不出来,只能背,然后发现剑指官方没有使用状态机,而且状态机也不是时间复杂度最优解,看了看评论区发现了简易写法,简单易懂good!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public boolean isNumber (String s) { if (s.length() == 0 ) return false ; s = s.trim(); boolean numFlag = false ; boolean dotFlag = false ; boolean eFlag = false ; for (int i = 0 ; i < s.length(); i++) { if (s.charAt(i) >= '0' && s.charAt(i) <= '9' ) numFlag = true ; else if (s.charAt(i) == '.' && !dotFlag && !eFlag) dotFlag = true ; else if ((s.charAt(i) == 'e' || s.charAt(i) == 'E' ) && !eFlag && numFlag){ eFlag = true ; numFlag = false ; } else if ((s.charAt(i) == '+' || s.charAt(i) == '-' ) && (i == 0 || s.charAt(i - 1 ) == 'e' || s.charAt(i - 1 ) == 'E' )) {} else return false ; } return numFlag; } }
调整数组顺序是奇数位于偶数前 题目:https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/
采用左右双指针,前提left小于right,left循环匹到偶数停止,然后right循环匹配到奇数停止,随后二者交换数值,多次循环后最后完成奇数排列于偶数前,但没有顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int [] exchange(int [] nums) { int left = 0 ,right = nums.length-1 ; while (left < right){ while (left<right && (nums[left] & 1 ) == 1 ){ left++; } while (left<right && (nums[right] & 1 ) == 0 ){ right--; } if (left<right){ nums[left] = nums[left] ^ nums[right]; nums[right] = nums[left] ^ nums[right]; nums[left] = nums[left] ^ nums[right]; } } return nums; } }
链表中倒数第k个节点 题目:https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/
左右指针遍历一次链表即可,先右指针空出k个位置给倒数节点,随后左右同时前进直到越界,因为遍历到表尾时,左指针在倒数节点的前一个节点,右指针在表尾节点,随后越界左指针就指向了倒数节点。注意空k个位置时要考虑越界的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public ListNode getKthFromEnd (ListNode head, int k) { ListNode left=head,right=head; for (int i=0 ;i<k;i++){ if (right == null ) return null ; right = right.next; } while (right != null ){ left = left.next; right = right.next; } return left; } }
反转链表 题目:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/
双指针,前缀指针pre、当前指针cur,next用来保存预更新值,首先保存下一结点的值给next,然后反转cur指向,最后更新pre、cur直到cur指向null,此时pre便是原表尾结点,返回pre即反转链表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public ListNode reverseList (ListNode head) { ListNode pre = null ,cur = head,next = null ; while (cur != null ){ next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; } }
合并两个排序的链表 题目:https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/
最近都在搞期末,没怎么写题,今天看题很简单,简单写完就发现是局域烂代码,太啰嗦了。。。感觉没啥进步。还是对着题解改进一下,md思路都一样别人的简洁很多。。。主要是最后的一个链表为空时,另一个链表直接连接的写法,我这很啰嗦,题解就很简明。
烂代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Solution { public ListNode mergeTwoLists (ListNode l1, ListNode l2) { ListNode cur = null ,head = null ; if (l1 == null && l2 == null ) return null ; if (l1 == null ) return l2; if (l2 == null ) return l1; if (l1.val <= l2.val){ head = l1; cur = head; l1 = l1.next; }else { head = l2; cur = head; l2 = l2.next; } while (l1 != null && l2 != null ){ if (l1.val <= l2.val){ cur.next = l1; cur = cur.next; l1 = l1.next; }else { cur.next = l2; cur = cur.next; l2 = l2.next; } } if (l1 == null ){ while (l2 != null ){ cur.next = l2; cur = cur.next; l2 = l2.next; } }else { while (l1 != null ){ cur.next = l1; cur = cur.next; l1 = l1.next; } } return head; } }
改进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public ListNode mergeTwoLists (ListNode l1, ListNode l2) { ListNode head = new ListNode(0 ),cur = head; while (l1 != null && l2 != null ){ if (l1.val <= l2.val){ cur.next = l1; l1 = l1.next; }else { cur.next = l2; l2 = l2.next; } cur = cur.next; } cur.next = l1 != null ? l1 : l2; return head.next; } }
树的子结构 题目:https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/
需要多写一个子树判断,也就是当前结点判断函数。我们先在a树中查找符合b子树根结点的点,查找到后再执行左右子树的判断。注意子树中b树为空说明子树查找完毕,a中有b子树,而b未空,a先空时说明查找失败,毕竟a已经无法继续了。这题相对于前序遍历,先判断当前结点是否有子树,然后看其左右结点是否有子树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public boolean isSubStructure (TreeNode a, TreeNode b) { if (a == null || b == null ) return false ; if (a.val == b.val && ( cur(a.left,b.left) && cur(a.right,b.right) )) return true ; return isSubStructure(a.left,b) || isSubStructure(a.right,b); } boolean cur (TreeNode a,TreeNode b) { if (b == null ) return true ; if (a == null ) return false ; if (a.val == b.val) return cur(a.left,b.left) && cur(a.right,b.right); else return false ; } }
题解改进版,超精简
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public boolean isSubStructure (TreeNode a, TreeNode b) { return (a != null && b != null ) && (cur(a,b) || isSubStructure(a.left,b) || isSubStructure(a.right,b)); } boolean cur (TreeNode a,TreeNode b) { if (b == null ) return true ; if (a == null || a.val != b.val) return false ; return cur(a.left,b.left) && cur(a.right,b.right); } }
二叉树的镜像 题目:https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/
说实话,简单题思路很容易想到,但就是写完有点啰嗦,大佬的解法是真简洁。
简单的前序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { TreeNode temp = null ; public TreeNode mirrorTree (TreeNode root) { preorder(root); return root; } public void preorder (TreeNode root) { if (root == null || (root.left == null && root.right == null ) ) return ; temp = root.left; root.left = root.right; root.right = temp; preorder(root.left); preorder(root.right); } }
大佬简洁易懂写法
1 2 3 4 5 6 7 8 9 class Solution { public TreeNode mirrorTree (TreeNode root) { if (root == null ) return root; TreeNode temp = root.right; root.right = mirrorTree(root.left); root.left = mirrorTree(temp); return root; } }
对称二叉树 题目:https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/
思路很简单,就是想不出来,我一开始做了一个镜像树与原树比较,属实笨比。然后看了下书发现想复杂了,直接树的左右结点对称比较即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public boolean isSymmetric (TreeNode root) { return mirror(root,root); } boolean mirror (TreeNode root1,TreeNode root2) { if (root1 == null && root2 == null ) return true ; if (root1 == null || root2 == null ) return false ; if (root1.val != root2.val) return false ; return mirror(root1.left,root2.right) && mirror(root1.right,root2.left); } }
顺时针打印矩阵 题目:https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/
很好想,但我自己写的时候对边界的判断很模糊,没有进行明确规定,写着写着就卡住了。看了下题解发现思路很清晰,特别是每次判断的时候使用 ++i 的格式,使得if语句很简洁,而且还更新了边界,所以下个循环的开始结点就更新好了,我一开始就是不知道怎么写这个结点更新导致卡壳了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public int [] spiralOrder(int [][] matrix) { if (matrix.length == 0 ) return new int [0 ]; int t=0 ,b=matrix.length-1 ,l=0 ,r=matrix[0 ].length-1 ,num=0 ; int [] ans = new int [(b+1 ) * (r+1 )]; while (true ){ for (int i=l;i<=r;i++) ans[num++] = matrix[t][i]; if (++t > b) break ; for (int i=t;i<=b;i++) ans[num++] = matrix[i][r]; if (--r < l) break ; for (int i=r;i>=l;i--) ans[num++] = matrix[b][i]; if (--b < t) break ; for (int i=b;i>=t;i--) ans[num++] = matrix[i][l]; if (++l > r) break ; } return ans; } }
包含min函数的栈 题目:https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/
使用辅助栈存储最小值即可,其他和普通栈无区别,注意出栈时栈顶元素匹配用equals,因为这里是两个栈顶元素在比较,也就是是对象在比较。
==
基本数据类型,比较值
引用数据类型(对象、数组),比较内存地址
所以这里肯定不能用==。
equals
不可用于基本数据类型的判断
当前类没有覆盖equals()方法,则使用equals()会相对于使用==,即比较两个对象的内存地址
若当前类覆盖equals()方法,则按照覆盖的方法来判断,如String的方法就是直接判断值(Integer、String、Date、File这四个覆写了)
所以我们使用equals(),因为这里是Integer包装类是覆盖过的equals(),可以比较值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class MinStack { Stack<Integer> a,b; public MinStack () { a = new Stack<Integer>(); b = new Stack<Integer>(); } public void push (int x) { a.push(x); if (b.empty() || b.peek() >= x) b.push(x); } public void pop () { if ( a.peek().equals(b.peek()) ) b.pop(); a.pop(); } public int top () { return a.peek(); } public int min () { return b.peek(); } }
栈的压入、弹出序列 题目:https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/
使用辅助栈同步操作,不能完全出栈的出栈序列就是错误的,故最后返回ans.empty()判断即可。我自己写的时候还想着O(n)做出来,结果就卡壳了,看见题解双循环就懂了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public boolean validateStackSequences (int [] pushed, int [] popped) { Stack<Integer> ans = new Stack<Integer>(); int i=0 ; for (int num : pushed){ ans.push(num); while (!ans.empty() && ans.peek().equals(popped[i]) ){ ans.pop(); i++; } } return ans.empty(); } }
从上到下打印二叉树(层序遍历) 题目:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/
就是层序遍历,使用队列辅助,由于队列先进先出,每次循环都是把下一层的结点从左到右加入队列,然后先进先出完成层序。队列要初始化加入根结点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int [] levelOrder(TreeNode root) { if (root == null ) return new int [0 ]; Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.add(root); List<Integer> list = new ArrayList<Integer>(); while (!queue.isEmpty()){ TreeNode node = queue.poll(); list.add(node.val); if (node.left != null ) queue.add(node.left); if (node.right != null ) queue.add(node.right); } int [] ans = new int [list.size()]; for (int i=0 ;i<list.size();i++) ans[i] = list.get(i); return ans; } }
从上到下打印二叉树(二) 题目:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/
我自己的做法就是普通的层序遍历,然后每层使用一个辅助队列保存当层应该打印的结点,然后打印并统计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public List<List<Integer>> levelOrder(TreeNode root) { Queue<TreeNode> queue = new LinkedList<TreeNode>(); List<List<Integer>> ans = new ArrayList<List<Integer>>(); if (root == null ) return ans; queue.add(root); while (!queue.isEmpty()){ List<Integer> exm = new ArrayList<Integer>(); Queue<TreeNode> q = new LinkedList<TreeNode>(); while (!queue.isEmpty()) q.add(queue.poll()); while (!q.isEmpty()){ TreeNode node = q.poll(); exm.add(node.val); if (node.left != null ) queue.add(node.left); if (node.right != null ) queue.add(node.right); } ans.add(exm); } return ans; } }
然后看了下k神的题解,进行了代码优化,循环里面使用初始队列长度来规定每层应该打印的次数,写法太强了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public List<List<Integer>> levelOrder(TreeNode root) { Queue<TreeNode> queue = new LinkedList<TreeNode>(); List<List<Integer>> ans = new ArrayList<List<Integer>>(); if (root != null ) queue.add(root); while (!queue.isEmpty()){ List<Integer> exm = new ArrayList<Integer>(); for (int i = queue.size();i > 0 ;i--){ TreeNode node = queue.poll(); exm.add(node.val); if (node.left != null ) queue.add(node.left); if (node.right != null ) queue.add(node.right); } ans.add(exm); } return ans; } }
从上到下打印二叉树(三) 题目:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/
和前两天的没有大差别,无非是奇偶数层要正序/倒序打印,但组件一开始写的时候犯难了,一开始想着加入一个辅助栈,结果后续元素入队列的顺序越写越乱。看了下题解发现直接用链表控制每层要打印的数据,对应奇偶层进行正倒序排列。还是LinkedList用少了,完全没想到它自带添加首尾的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public List<List<Integer>> levelOrder(TreeNode root) { Queue<TreeNode> queue = new LinkedList<TreeNode>(); List<List<Integer>> ans = new ArrayList<List<Integer>>(); if (root != null ) queue.add(root); int j=1 ; while (!queue.isEmpty()){ LinkedList<Integer> temp = new LinkedList<Integer>(); for (int i=queue.size();i>0 ;i--){ TreeNode node = queue.poll(); if ((j & 1 ) == 1 ) temp.addLast(node.val); else temp.addFirst(node.val); if (node.left != null ) queue.add(node.left); if (node.right != null ) queue.add(node.right); } j++; ans.add(temp); } return ans; } }
二叉搜索树的后续遍历序列 题目:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
注意二叉搜索树左小右大,查找左右子树的分割点再进行递归判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public boolean verifyPostorder (int [] postorder) { return recur(postorder, 0 , postorder.length-1 ); } boolean recur (int [] postorder,int left,int root) { if (left >= root) return true ; int cur = left; while (postorder[cur] < postorder[root]) cur++; int mid = cur; while (postorder[cur] > postorder[root]) cur++; return cur == root&&recur(postorder,left,mid-1 )&&recur(postorder,mid,root-1 ); } }
二叉树中和为某一值的路径 题目:https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/
我们使用先序遍历递归查找路径,保存路径值以便判断符合题目的路径。注意添加符合结果的路径需要新建一个对象,否则直接添加的path是同一个对象,后续会被覆盖。最后退出该层递归时,我们应删除当前结点,方便更新后续的路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { List<List<Integer>> ans = new ArrayList<>(); LinkedList<Integer> path = new LinkedList<Integer>(); public List<List<Integer>> pathSum(TreeNode root, int target) { preorder(root,target); return ans; } void preorder (TreeNode root, int target) { if (root == null ) return ; path.add(root.val); target -= root.val; if (target == 0 && root.left == null && root.right == null ) ans.add(new LinkedList(path)); preorder(root.left,target); preorder(root.right,target); path.removeLast(); } }
复杂链表的复制 题目:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/
先是拼接新旧链表,然后巧妙的使用链表的指向特性给新链表的结点赋值,最后再将原链表和新链表拆分开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Solution { public Node copyRandomList (Node head) { if (head == null ) return null ; Node cur = head; while (cur != null ){ Node temp = new Node(cur.val); temp.next = cur.next; cur.next = temp; cur = temp.next; } cur = head; while (cur != null ){ if (cur.random != null ) cur.next.random = cur.random.next; cur = cur.next.next; } cur = head.next; Node ori = head, ans = head.next; while (cur.next != null ){ ori.next = ori.next.next; cur.next = cur.next.next; ori = ori.next; cur = cur.next; } ori.next = null ; return ans; } }
二叉搜索树与双向链表 题目:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/
注意是要构造双向循环链表,最后首尾指向也要更新。
然后使用中序遍历做即可,注意pre、head结点的初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { Node pre,head; public Node treeToDoublyList (Node root) { if (root == null ) return null ; inorder(root); pre.right = head; head.left = pre; return head; } void inorder (Node cur) { if (cur == null ) return ; inorder(cur.left); if (pre != null ) pre.right = cur; else head = cur; cur.left = pre; pre = cur; inorder(cur.right); } }
序列化二叉树 题目:https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/
本题类似层序遍历的使用,先将二叉树转层序遍历的字符串形式,后使用层序遍历的字符串重新转变成二叉树。我们需要数以拼接时末尾的逗号应该去掉,转变二叉树时字符串的首尾是中括号,不能要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class Codec { public String serialize (TreeNode root) { if (root == null ) return "[]" ; StringBuilder ans = new StringBuilder("[" ); Queue<TreeNode> queue = new LinkedList<>(){{ add(root); }}; while (!queue.isEmpty()){ TreeNode node = queue.poll(); if (node != null ){ ans.append(node.val+"," ); queue.add(node.left); queue.add(node.right); }else { ans.append("null," ); } } ans.deleteCharAt(ans.length() - 1 ); ans.append("]" ); return ans.toString(); } public TreeNode deserialize (String data) { if (data.equals("[]" )) return null ; String[] vals = data.substring(1 ,data.length()-1 ).split("," ); TreeNode root = new TreeNode(Integer.parseInt(vals[0 ])); Queue<TreeNode> queue = new LinkedList<TreeNode>(){{ add(root); }}; int i = 1 ; while (!queue.isEmpty()){ TreeNode node = queue.poll(); if (!vals[i].equals("null" )){ node.left = new TreeNode(Integer.parseInt(vals[i])); queue.add(node.left); } i++; if (!vals[i].equals("null" )){ node.right = new TreeNode(Integer.parseInt(vals[i])); queue.add(node.right); } i++; } return root; } }
字符串的排列(回溯法) 题目:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/
其实思想就是传统的回溯,奈何之前算法了解片面,没用看相关书籍,回溯的原理似懂非懂,今天就mark一下,准备记录算法基础的了解与学习。
也就是每当我们递归前改变了”字符串”的状态,在结束当前递归后需撤销该状态改变,因为后续还要基于该层的最初基础也就是上一层的状态来改变,所以要回溯,这里仅限个人理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { List<String> ans = new LinkedList<String>(); char [] c; public String[] permutation(String s) { c = s.toCharArray(); dfs(0 ); return ans.toArray(new String[ans.size()]); } void dfs (int x) { if (x == c.length-1 ){ ans.add(String.valueOf(c)); return ; } HashSet<Character> set = new HashSet<Character>(); for (int i=x;i < c.length;i++){ if (set.contains(c[i])) continue ; set.add(c[i]); swap(i,x); dfs(x+1 ); swap(i,x); } } void swap (int a,int b) { char temp = c[a]; c[a] = c[b]; c[b] = temp; } }
评论区精简版,还可剪枝优化,但比交换看起来好理解一点,递归推到最外层时继续循环,然后就把第二个先标记了,随后的递归第二个就先添加了,然后依此类推。就是在依次固定首尾,其实思想都一样,就是有个交换看起来更复杂了。所以说剑指Offer书上为什么要交换?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public String[] permutation(String s) { Set<String> list = new HashSet<>(); char [] arr = s.toCharArray(); StringBuilder sb = new StringBuilder(); boolean [] visited = new boolean [arr.length]; dfs(arr, "" , visited, list); return list.toArray(new String[0 ]); } public void dfs (char [] arr, String s, boolean [] visited, Set<String> list) { if (s.length() == arr.length){ list.add(s); return ; } for (int i=0 ; i<arr.length; i++){ if (visited[i]) continue ; visited[i] = true ; dfs(arr, s+String.valueOf(arr[i]), visited, list); visited[i] = false ; } } }
数组中出现次数超过一半的数字 题目:https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/
基础做法就是使用哈希表,遍历数组,键就是数组元素,值是该元素出现的次数,统计好后再遍历一次哈希表找到值最大的键输出即可。
取巧点就排序数组( Arrays.sort() ),然后输出中间值,因为题目说该数字出现次数超过了数组的一半,排序后的中间值一定就是答案。
然后更巧妙的算法可以看大佬题解,例如摩尔投票法等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int majorityElement (int [] nums) { int max = 0 ,ans = 0 ; Map<Integer,Integer> map = new HashMap<Integer,Integer>(); for (int i=0 ;i<nums.length;i++){ map.put(nums[i], map.getOrDefault(nums[i], 0 ) + 1 ); } for (Map.Entry<Integer,Integer> entry : map.entrySet()){ if (entry.getValue() > max){ max = entry.getValue(); ans = entry.getKey(); } } return ans; } }
摩尔投票法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int majorityElement (int [] nums) { int vote = 0 , mode = 0 ; for (int num : nums){ if (vote == 0 ) mode = num; if (mode == num) vote += 1 ; else vote -= 1 ; } return mode; } }
最小的k个数 题目:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/
弱智写法:
1 2 3 4 5 6 7 8 9 10 class Solution { public int [] getLeastNumbers(int [] arr, int k) { Arrays.sort(arr); int [] ans = new int [k]; for (int i=0 ;i<k;i++){ ans[i] = arr[i]; } return ans; } }
快排:
基础算法要从头过一遍了,都忘了😂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public int [] getLeastNumbers(int [] arr, int k) { quickSort(arr,0 ,arr.length-1 ); return Arrays.copyOf(arr,k); } private void quickSort (int [] arr,int l,int r) { if (l>r) return ; int i = l,j = r; while (i<j){ while (i<j && arr[j] >= arr[l]) j--; while (i<j && arr[i] <= arr[l]) i++; swap(arr,i,j); } swap(arr,l,j); quickSort(arr,l,i-1 ); quickSort(arr,j+1 ,r); } private void swap (int [] arr,int i,int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
其实完全遍历一遍排序后时间都差不多,来看看大佬的优化,限制了返回的长度,只需要找出前k个元素来排序即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int [] getLeastNumbers(int [] arr, int k) { if (k >= arr.length) return arr; return quickSort(arr, k, 0 , arr.length - 1 ); } private int [] quickSort(int [] arr, int k, int l, int r) { int i = l, j = r; while (i < j) { while (i < j && arr[j] >= arr[l]) j--; while (i < j && arr[i] <= arr[l]) i++; swap(arr, i, j); } swap(arr, i, l); if (i > k) return quickSort(arr, k, l, i - 1 ); if (i < k) return quickSort(arr, k, i + 1 , r); return Arrays.copyOf(arr, k); } private void swap (int [] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } }
数据流中的中位数(堆排序) 题目:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/
使用优先队列实现大小堆,可以让查找中间值变为O1,因为中间值只会和大小堆顶元素产生关系。做这题前可以先去了解一下优先队列的规则,然后就明白为什么用它初始化大小堆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class MedianFinder { Queue<Integer> max,min; public MedianFinder () { max = new PriorityQueue<>(); min = new PriorityQueue<>(Comparator.reverseOrder()); } public void addNum (int num) { if (max.size() != min.size()){ max.add(num); min.add(max.poll()); }else { min.add(num); max.add(min.poll()); } } public double findMedian () { return max.size() != min.size() ? max.peek() : (max.peek() + min.peek())/2.0 ; } }
连续子数组的最大值 题目:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/
没有使用dp,直接分析数组,当前和为正数时,直接比较取最大值,若当前和为负数且小于待加的数就舍弃。(因为有全是负数的情况,所以和为负数不能直接舍弃)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int maxSubArray (int [] nums) { int max=Integer.MIN_VALUE,sum=0 ; for (int num : nums){ if (sum<num && sum<0 ){ sum=0 ; } sum += num; max = Math.max(max,sum); } return max; } }
1~n整数中1出现的次数 题目:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/
我们将整个数分成3部分,高位、当前位、低位,然后逐位进行1数量的累加
当前位=0,说明当前位无1,低位的改变不影响当前位1的数量,数量是high * 该位的幂因子
当前位=1,当前位可与低位配合增加1出现的数量(1000~1XXX),一共有XXX+1个,也就是low+1,然后加上高位的组合,数量是high * 幂因子+low+1
当前位=29,当前位可与低位配合增加1出现的数量(X000XXXX),X>1所以10001999这个区间被包含,也只有该区间有1出现,所以29都是一个情况,而此时低位组合后1出现次数为 该位的幂因子(1019 = 10,100199 = 100,1000~1999 = 1000 ·····),所以数量是high * 幂因子 + 幂因子,即(high+1) * 幂因子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int countDigitOne (int n) { int factor=1 ,ans=0 ,high = n/10 ,cur = n%10 ,low = 0 ; while (high != 0 || cur != 0 ){ if (cur == 0 ) ans += high * factor; else if (cur == 1 ) ans += high * factor + low + 1 ; else ans += (high + 1 ) * factor; low += cur * factor; cur = high % 10 ; high = high / 10 ; factor *= 10 ; } return ans; } }
数字序列中某一位的数字 题目:https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/
用了K神题解的图,先分析数字位数区间,起始位,数的数量(数字),数字数量(数位),得出关系式,然后用n减去低位数的数字数量直到<0,此时说明现在的位数就是n的位数,然后再找n处于哪一个数的某个位置上,最后转换成int输出。注意start是第0个数,所以后续使用n-1计算,而不是n 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int findNthDigit (int n) { int digit = 1 ; long start = 1 ; long count = 9 ; while (n > count){ n -= count; digit++; start *= 10 ; count = 9 * start * digit; } long num = start + (n-1 )/digit; return Long.toString(num).charAt((n-1 ) % digit) - '0' ; } }
把数组排成最小的数 题目:https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/
这题本质就是快排,只不过判断条件的书写需要注意一下。排序其实还是有点生疏,得抽空把排序练熟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public String minNumber (int [] nums) { String[] strs = new String[nums.length]; for (int i = 0 ;i<nums.length;i++) strs[i] = String.valueOf(nums[i]); quickSort(strs,0 ,strs.length-1 ); StringBuilder ans = new StringBuilder(); for (String s : strs) ans.append(s); return ans.toString(); } void quickSort (String[] strs,int l,int r) { if (l > r) return ; int i = l,j = r; String temp = strs[i]; while (i < j){ while ((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) j--; while ((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) i++; temp = strs[i]; strs[i] = strs[j]; strs[j] = temp; } strs[i] = strs[l]; strs[l] = temp; quickSort(strs,l,i-1 ); quickSort(strs,i+1 ,r); } }
把数字翻译成字符串 题目:https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/
本质就是一个动态规划的题目,需要找到dp的规律。这里借用一下k神的图来理解。
1i-1也就是前i-1个数的方案记为f(i-1),推断出单独翻译第i个数时,有f(i-1)个方案,因为单独翻译i只有1种,其方案数由前i-1来决定。而题目种有26个字母,也就是两个数组合在[10,25]这个区间时,我们组合出新的方案,例:i-1i两个组合成一个可被翻译的数,其方案数为f(i-2),由前i-2来决定。
所以计算f(i),分两种情况,i-1i可以组合,f(i) = f(i-2) + f(i-1);i-1i不可组合,f(i) = f(i-1)。通过字符串的分割和比较来实现区间判断。substring(i-2,i)是i-2、i-1的组合字符串。
假设dp[2]也就是前两个数可组合,得到dp[2]=dp[0]+dp[1],明确知道dp[2]=2,dp[1]=1,倒推出dp[0]=1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int translateNum (int num) { String s = String.valueOf(num); int [] dp = new int [s.length()+1 ]; dp[0 ] = 1 ; dp[1 ] = 1 ; for (int i = 2 ;i < s.length()+1 ;i++){ String temp = s.substring(i-2 ,i); if (temp.compareTo("10" ) >= 0 && temp.compareTo("25" ) <=0 ) dp[i] = dp[i-2 ] + dp[i-1 ]; else dp[i] = dp[i-1 ]; } return dp[s.length()]; } }
礼物的最大价值 题目:https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/
标准的动态规划题目,找到转义方程就很好做了,注意我们可以直接在原来的二维数组上覆盖值变成答案,最后直接返回最后一个元素即可。直接覆盖比较节省空间,然后分四种处理情况,左上角初始值不变,上和左靠边的值只受单方向影响,其余值则累加左和上的最大值。左和上对应 只能右和下移动。
有两种方法,一种是直接遍历数组,分情况判断,但多执行多次边情况的判断语句,所以另一种方案是先初始化边上值,然后遍历其余元素即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int maxValue (int [][] grid) { int m = grid.length, n = grid[0 ].length; for (int j=1 ;j < n;j++) grid[0 ][j] += grid[0 ][j-1 ]; for (int i=1 ;i < m;i++) grid[i][0 ] += grid[i-1 ][0 ]; for (int i=1 ;i<m;i++) for (int j=1 ;j<n;j++) grid[i][j] += Math.max(grid[i-1 ][j],grid[i][j-1 ]); return grid[m-1 ][n-1 ]; } }
最长不含重复字符的子字符串 题目:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/
这题书上用的是动态规划,但我一开始想到的思路就是滑动窗口的,所以就没有选择动态规划的做法,用哈希表来进行滑动窗口,我一开始还在那捣鼓数组和字符串,根本没想到用Map容器比较好。
基本思路就是遍历一次字符串,无重复右边界++,记录长度最大值,有重复则修改左边界然后记录最大值,这里关键就是这个左边界的更新。使用的是下面这个语句,一开始不知道为什么要取max,然后发现了一个特殊情况**”abba”**,max就是为了应对这种情况,详细看下面分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int lengthOfLongestSubstring (String s) { Map<Character,Integer> map = new HashMap<>(); int l = 0 ,r = 0 ,ans = 0 ; while (r < s.length()){ char cur = s.charAt(r); if (map.containsKey(cur)){ l = Math.max(l, map.get(cur) + 1 ); } map.put(s.charAt(r),r); ans = Math.max(ans,r-l+1 ); r++; } return ans; } }
丑数 题目:https://leetcode-cn.com/problems/chou-shu-lcof/
按照2、3、5的倍数,从1开始,模拟构造整个丑数序列,返回第n个数,其中每次返回乘积最小值作为这一个序列的值,给每一个倍数都标记一个索引值,即代表处于数组的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int nthUglyNumber (int n) { int i2=0 ,i3=0 ,i5=0 ; int [] res = new int [n]; res[0 ] = 1 ; for (int i=1 ;i<n;i++){ int ans2 = res[i2] * 2 ; int ans3 = res[i3] * 3 ; int ans5 = res[i5] * 5 ; res[i] = Math.min(ans2,Math.min(ans3,ans5)); if (ans2 == res[i]) i2++; if (ans3 == res[i]) i3++; if (ans5 == res[i]) i5++; } return res[n-1 ]; } }
第一个只出现一次的字符 题目:https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/
注意这里键值对使用的是布尔值,方便后续判断返回,如果记数统计,后续还要加一个判断语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public char firstUniqChar (String s) { Map<Character,Boolean> map = new HashMap<>(); char [] str = s.toCharArray(); for (char c : str) map.put(c, !map.containsKey(c)); for (char c : str) if (map.get(c)) return c; return ' ' ; } }
有序哈希表,使用LinkedHashMap这个特殊结构,保证哈希表的存储是有序的,最后我们直接遍历哈希表的即可,因为哈希表去重,我们不用重复访问同一字符,比遍历字符串可能快一点。
1 2 3 4 5 6 7 8 9 10 11 class Solution { public char firstUniqChar (String s) { Map<Character,Boolean> map = new LinkedHashMap<>(); char [] str = s.toCharArray(); for (char c : str) map.put(c, !map.containsKey(c)); for (Map.Entry<Character,Boolean> entry : map.entrySet()) if (entry.getValue()) return entry.getKey(); return ' ' ; } }
数组中的逆序对 题目:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/
归并排序题目,下面写了K神的注释版,这个看明白后可以再看一下另外一个版本,我感觉看起来思路会更加清晰。
K神:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { int [] nums, temp; public int reversePairs (int [] nums) { this .nums = nums; temp = new int [nums.length]; return mergeSort(0 ,nums.length-1 ); } private int mergeSort (int l, int r) { if (l >= r) return 0 ; int m = (l + r) / 2 ; int ans = mergeSort(l,m) + mergeSort(m+1 ,r); for (int k = l; k <= r; k++) temp[k] = nums[k]; int i = l, j = m + 1 ; for (int k = l; k <= r; k++){ if (i == m + 1 ) nums[k] = temp[j++]; else if (j == r + 1 || temp[i] <= temp[j]) nums[k] = temp[i++]; else { nums[k] = temp[j++]; ans += m - i + 1 ; } } return ans; } }
评论区版本,理解归并排序后,我感觉这个写的更清晰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { int count = 0 ; public int reversePairs (int [] nums) { int len = nums.length; if (len < 2 ) return 0 ; mergesort(nums, 0 , len - 1 ); return count; } public int [] mergesort(int [] nums, int l, int h){ if (l == h) return new int []{nums[l]}; int mid = l + (h - l) / 2 ; int [] left = mergesort(nums, l, mid); int [] right = mergesort(nums, mid + 1 , h); int [] res = new int [h - l + 1 ]; int i = 0 , j = 0 , m = 0 ; while (i < left.length && j < right.length) { if (left[i] <= right[j]){ res[m++] = left[i++]; }else { res[m++] = right[j++]; count += left.length - i; } } while (i < left.length) res[m++] = left[i++]; while (j < right.length) res[m++] = right[j++]; return res; } }
两个链表的第一个公共节点 题目:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
通过数学思维来简化代码,很巧妙,语句也很简便易懂。可以看看题解的动图详解:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Solution { public ListNode getIntersectionNode (ListNode headA, ListNode headB) { ListNode A = headA,B = headB; while (A != B){ A = A!=null ? A.next : headB; B = B!=null ? B.next : headA; } return A; } }
在排序数组中查找数字 题目:https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/
这里使用二分法加快时间效率,需要注意中间值与目标值相等时,后续的边界该如何判断,查找重复目标数的右边界就归于小于情况,查找左边界归于大于情况,然后退出循环的边界复制可以去看K神题解的动图,边界好理解退出循环时l、r的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public int search (int [] nums, int target) { int l=0 ,r=nums.length-1 ; while (l<=r){ int mid = (l + r)/2 ; if (nums[mid] <= target) l = mid+1 ; else r = mid-1 ; } int right = r; if (r >= 0 && nums[r] != target) return 0 ; l=0 ;r=nums.length-1 ; while (l<=r){ int mid = (l + r)/2 ; if (nums[mid] < target) l = mid+1 ; else r = mid-1 ; } int left = l; return right - left + 1 ; } }
隔了好久回来刷题,发现下面简写版本更易理解,说实话这题二分想的挺多的,不如遍历😂,但是思想要到位。遍历也可以剪枝的,当值大于目标就可以直接退出了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int search (int [] nums, int target) { return local(nums, target) - local(nums, target - 1 ); } public int local (int [] nums, int target) { int l = 0 , r = nums.length - 1 ; while (l <= r){ int m = (l + r) / 2 ; if (nums[m] <= target) l = m + 1 ; else r = m - 1 ; } return l; } }
0~n-1中缺失的数字 题目:https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/
K神:排序数组的搜索问题,优先二分法、双指针。要注意退出循环后的两个指针的位置,然后判断返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int missingNumber (int [] nums) { int l = 0 ,r = nums.length-1 ; while (l<=r){ int mid = (l+r)/2 ; if (nums[mid] == mid) l = mid + 1 ; else r = mid - 1 ; } return l; } }
二叉搜索树的第k大结点 题目:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/
因为是二叉搜索树,左小右大,我们使用右根左的中序遍历,递归二叉树后,数组记录从大到小的根结点值,返回第k-1个结点即可。但还可以优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { List<Integer> ans = new ArrayList<Integer>(); public int kthLargest (TreeNode root, int k) { inorder(root); return ans.get(k-1 ); } void inorder (TreeNode root) { if (root == null ) return ; inorder(root.right); ans.add(root.val); inorder(root.left); } }
递归时实时更新k值,当k==0时就纪录当前根节点,随后的递归全部直接退出,这样可以不用遍历完二叉树。走到第k个就返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { int ans,k; public int kthLargest (TreeNode root, int k) { this .k = k; inorder(root); return ans; } void inorder (TreeNode root) { if (root == null ) return ; inorder(root.right); if (k == 0 ) return ; if (--k == 0 ) ans = root.val; inorder(root.left); } }
二叉树的深度 题目:https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/
easy题,记录深度状态,每次叶子结点就更新深度,最后返回深度即可。(也就是所有路径长度的最大值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { int depth=0 ,ans; public int maxDepth (TreeNode root) { tree(root); return ans; } void tree (TreeNode root) { if (root == null ){ ans = Math.max(depth,ans); return ; } depth++; tree(root.right); tree(root.left); depth--; } }
平衡二叉树 题目:https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/
自底向上,我们先走到叶子结点,然后返回上一层,并记录左右子树的深度,然后每走到一个子树根结点就判断其子树是否为AVL,如果是就继续返回上层进行判断,如果不是就返回-1来标记不是AVL,且每次计算子树深度后都判断该值是否为-1,如果是-1则说明这棵树不是AVL,然后就可以直接返回进行剪枝操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public boolean isBalanced (TreeNode root) { return balance(root) != -1 ; } private int balance (TreeNode root) { if (root == null ) return 0 ; int left = balance(root.left); if (left == -1 ) return -1 ; int right = balance(root.right); if (right == -1 ) return -1 ; return Math.abs(left - right) <= 1 ? Math.max(left,right)+1 : -1 ; } }
数组中数字出现的次数(一) 题目:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/
规定O(n)的时间复杂度,我们不可以使用Map键值对,这里很巧妙使用了异或以及按位与的位运算。先通过异或找到两个不相等数的异或和;然后按位与找到异或和中的差异位,这就是两个不相等数的不同位,只有其中一个数有这个位数;接着我们就把数组以该位数分成两部分,一部分有这一位,另一部分没有这一位,并异或计算;由于其他数都是成对出现,异或后都会变为0不影响结果,我们通过差异位分组异或就得到了两个不相等的数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int [] singleNumbers(int [] nums) { int ans1=0 ,ans2=0 ,a=0 ,b=1 ; for (int num : nums) a ^= num; while ( (a & b) == 0 ) b <<= 1 ; for (int num : nums){ if ( (num & b) == 0 ) ans1 ^= num; else ans2 ^= num; } return new int [] {ans1,ans2}; } }
数组中数字出现的次数(二) 题目:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/
思路就是把数组每一个数逐位判断,统计该位==1的次数,然后和3取余,由于其他数都是3个一对,只有一个数是单独的,所以当我们取余后与1判断,如果等于1说明这一位是单独数所包含的,然后就按位 或运算 给结果的这一位数赋值。逐位判断后即可得到这个单独的数。
其他的简答方法,哈希表存储后查找值==1的键;使用排序算法将数组排序,然后有三种情况:ABBBCCC、BBBACCC、BBBCCCA,我们就分三种情况讨论,若第一个!=第二个,最后一个不等于倒数第二个,遍历数组的中间值不等于前后两个数,那么第一个、最后一个、中间值就是三种情况下的单独数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int singleNumber (int [] nums) { int res = 0 ; for (int i=0 ;i<32 ;i++){ int count = 0 ; for (int j = 0 ;j < nums.length;j++) count += (nums[j] >>> i) & 1 ; if (count % 3 == 1 ) res |= 1 << i; } return res; } }
和为s的两个数字 题目:https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/
简单题,左右双指针解决。还有哈希表存值匹配的做法,就是两数之和那题的解法,不过这里数据更多O(n)很慢了,我们可以借助排序特性,使用左右双指针只遍历一次数组实现O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int [] twoSum(int [] nums, int target) { int l = 0 ,r = nums.length-1 ; while (l<r){ if (nums[l] + nums[r] > target) r--; else if (nums[l] + nums[r] < target) l++; else return new int []{nums[l],nums[r]}; } return new int [0 ]; } }
和为s的连续正数序列 题目:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/
题目思路很简单,滑动窗口。但最后list转数组让我犯了愁,可以看看下面这个文章,写的比较清楚:
https://juejin.cn/post/6844904145753735176
也就是说我们使用toArray方法,不带参数返回Object类型,带参数就返回参数类型,由于Object是不能直接强转其他类型的,所以我们使用**toArray(T[] a)**这个重载方法参数就是我们需要返回的类型,而这个参数数组一般大小设为0,Java为这一块进行过优化,如果设置大小偏大或偏小都要重新分配,还不如一开始设为0,且大小设置相等时,遇见高并发情况也会出现问题,所以我们直接设大小为0是性能最优的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int [][] findContinuousSequence(int target) { int l=1 ,r=2 ,sum=3 ; List<int []> ans = new ArrayList<>(); while (l < r){ if (sum == target){ int [] res = new int [r-l+1 ]; for (int i = l;i <= r;i++) res[i-l] = i; ans.add(res); sum += ++r; } if (sum < target) sum += ++r; else if (sum > target) sum -= l++; } return ans.toArray(new int [0 ][]); } }
翻转单词顺序 题目:https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/
双指针的思路很简单,而且K神还想到了分割字符串后倒叙拼接,我一开始是倒叙+栈😂,慢哭了。看到双指针就明白优化的方法了,一开始还没想到,而且分割+倒叙拼接也很巧妙。有思路后马上就懂了,就是需要注意的空格的处理,自己写的时候没考虑周全,为了满足空格的处理情况加了很多不必要判断。
我的脑抽写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public String reverseWords (String s) { if (s.equals("" )) return "" ; StringBuilder sb = new StringBuilder(); Stack<Character> stack = new Stack<>(); char [] temp = s.toCharArray(); if (temp[temp.length-1 ] != ' ' ) stack.push(temp[temp.length-1 ]); for (int i=temp.length-2 ;i>=0 ;i--){ if (temp[i] == ' ' ){ if (temp[i+1 ] == ' ' ) continue ; while (!stack.isEmpty()) sb.append(stack.pop()); sb.append(' ' ); }else { stack.push(temp[i]); } } while (!stack.isEmpty()) sb.append(stack.pop()); return sb.toString().trim(); } }
双指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public String reverseWords (String s) { s = s.trim(); int r = s.length()-1 ,l = r; StringBuilder ans = new StringBuilder(); while (l>=0 ){ while (l>=0 && s.charAt(l) != ' ' ) l--; ans.append(s.substring(l+1 ,r+1 ) + ' ' ); while (l>=0 && s.charAt(l) == ' ' ) l--; r = l; } return ans.toString().trim(); } }
分割+倒叙拼接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public String reverseWords (String s) { String[] strs = s.trim().split(" " ); StringBuilder ans = new StringBuilder(); for (int i = strs.length-1 ;i >= 0 ;i--){ if (strs[i].equals("" )) continue ; ans.append(strs[i] + " " ); } return ans.toString().trim(); } }
左旋转字符串 题目:https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/
简单题,左右截取,反转拼接即可。
1 2 3 4 5 6 7 8 9 10 class Solution { public String reverseLeftWords (String s, int n) { String left = s.substring(0 ,n); String right = s.substring(n,s.length()); right += left; return right; } }
滑动窗口的最大值 题目:https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/
滑动窗口的思路其实很好想,我一开始也想到了先找一次max,然后每次与新的右边界比较依照结果更新max,如果max是左边界则要重新找max,因为max已不存在更新后的滑动窗口。但在数据存放上没有找到一个好都容器。这里K神使用双端队列就很巧妙解决问题,写的也很好懂,特别是左边界情况的判断这一步写的很巧妙。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public int [] maxSlidingWindow(int [] nums, int k) { if (nums.length == 0 || k == 0 ) return new int [0 ]; Deque<Integer> deque = new LinkedList<>(); int [] ans = new int [nums.length - k + 1 ]; for (int i = 0 ; i < k; i++){ while (!deque.isEmpty() && deque.peekLast() < nums[i]) deque.removeLast(); deque.addLast(nums[i]); } ans[0 ] = deque.peekFirst(); for (int i = k; i < nums.length; i++){ if (deque.peekFirst() == nums[i - k]) deque.removeFirst(); while (!deque.isEmpty() && deque.peekLast() < nums[i]) deque.removeLast(); deque.addLast(nums[i]); ans[i - k + 1 ] = deque.peekFirst(); } return ans; } }
队列的最大值 题目:https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/
构造一个正常的先进先出队列queue,然后再构造一个双端队列deque进行两头操作来维护最大值,和昨天题目一样,值在deque中从大到小排序。
queue就是正常入队出队,deque需要进行最大值维护。
queue入队时,若当前值>deque末值,末值出队,将deque所有小于当前值的值都出队后,当前值入deque队尾。
queue出队时,若当前值=deque首位,首位也要出队,毕竟现在最大值已不存在queue中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class MaxQueue { Deque<Integer> deque; Queue<Integer> queue; public MaxQueue () { deque = new LinkedList<>(); queue = new LinkedList<>(); } public int max_value () { return deque.isEmpty() ? -1 : deque.peekFirst(); } public void push_back (int value) { queue.add(value); while (!deque.isEmpty() && deque.peekLast() < value) deque.pollLast(); deque.addLast(value); } public int pop_front () { if (queue.isEmpty()) return -1 ; if (queue.peek().equals(deque.peekFirst())) deque.pollFirst(); return queue.poll(); } }
n个骰子的点数 题目:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/
做这道题首先是理解题目意思,然后理解暴力破解思路,然后动态规划的做法就很好理解了。这道题就是逐渐加入骰子,然后求有n个骰子时其点数产生的范围是什么,然后求处这个范围里的值的分布概率,按序输出值的分布概率。下面贴一下K神题解的图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public double [] dicesProbability(int n) { double [] cur = new double [6 ]; Arrays.fill(cur,1.0 /6.0 ); for (int i = 2 ; i <= n; i++){ double [] temp = new double [5 * i + 1 ]; for (int j = 0 ; j < cur.length; j++){ for (int k = 0 ; k < 6 ; k++){ temp[j+k] += cur[j]/6.0 ; } } cur = temp; } return cur; } }
扑克牌中的顺子 题目:https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/
这道题可以想到很复杂,但解题关键就一句话,max-min<5,满足这个判断的无重复序列即可组成顺子,不得不说K神这规律挺会找啊,我一开始还在纠结怎么判断顺子,然而K神直接从结果出发,很容易解决了问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public boolean isStraight (int [] nums) { Set<Integer> set = new HashSet<>(); int max = 0 , min = 14 ; for (int num : nums){ if (num == 0 ) continue ; max = Math.max(max,num); min = Math.min(min,num); if (set.contains(num)) return false ; set.add(num); } return max - min < 5 ; } }
圆圈中最后剩下的数字(约瑟夫环) 题目:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/
数学问题,这个解法只能说是看得懂讲不清楚的🤣,关键就是推导公式cur = (cur + m) % i,理解了这个题目就很容易了。注意我们解法就是倒推,这里贴一下K神的图,看图比较好理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int lastRemaining (int n, int m) { int cur = 0 ; for (int i = 2 ; i <= n; i++){ cur = (cur + m) % i; } return cur; } }
股票的最大利润 题目:https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/
这道题自己想到了非暴力破解思路就试着写写看,没想到过了,然后再去学习一下K神的题解,没想到大佬的更简洁😂,动态规划的思想还需要锻炼。
其实我的思想和K神的差不多,时间、空间效率算下来也差不多,就是写的有点冗杂了。,可以稍稍精简一下。然后说一下题目思路,利润主要讲究低买高卖,我们维护最小值来记录最大利润即可。当前最小值一有更新就使用新的min,最后返回利润即可。
自己的解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int maxProfit (int [] prices) { if (prices.length == 0 ) return 0 ; int min_key = 0 , min_value = prices[0 ], profit = 0 , ans = 0 , cur = 0 ; while (cur < prices.length){ if (min_key == prices.length-1 ) return ans; for (int i = min_key + 1 ; i < prices.length; i++){ if (prices[i] < min_value){ min_key = i; min_value = prices[i]; break ; }else { profit = prices[i] - min_value; ans = Math.max(ans,profit); } cur++; } } return ans; } }
K神:
1 2 3 4 5 6 7 8 9 10 class Solution { public int maxProfit (int [] prices) { int min = Integer.MAX_VALUE, profit = 0 ; for (int price : prices){ min = Math.min(min,price); profit = Math.max(profit,price - min); } return profit; } }
求1+2+…+n 题目:https://leetcode-cn.com/problems/qiu-12n-lcof/
由于限制了运算符,我们使用&&逻辑运算符的短路特性,即A&&B,若A为falseB就不会判断了,所以我们通过一个布尔值来确实 n==1 时退出递归。
1 2 3 4 5 6 7 8 9 class Solution { int sum = 0 ; public int sumNums (int n) { boolean x = n > 1 && sumNums(n - 1 ) > 1 ; sum += n; return sum; } }
不用加减乘除做加法 题目:https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/
题目一上来就禁止了+-*/,我们很自然就想到要用位运算,但怎么用就不知道了,看了下K神的题解,解析的很详细。我们把加法过程分为不进位和进位两种情况。
不进位:此时我们只需要通过异或 运算计算01->1、00->0、11->0,11此时要进位,不能记为1,所以没有使用按位与,然后就算出了当前不用进位的和。
进位:通过按位与和移位运算,我们将同为1的位标记出来,并实现进位,也就是将其高的一位记为1,也就是左移1
最后我们又将进位后的数与无进位和进行循环计算,直到没有进位,此时的无进位和就是两数的加法结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public int add (int a, int b) { while (b != 0 ){ int carry = (a & b) << 1 ; a ^= b; b = carry; } return a; } } class Solution { public int add (int a, int b) { int carry = (a & b) << 1 , sum = a ^ b; while (carry != 0 ){ a = sum; b = carry; carry = (a & b) << 1 ; sum = a ^ b; } return sum; } }
构建乘积数组 题目:https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/
看到题目我以为又是用位运算模拟,然后看了题解发现解法很巧妙,暴力解法肯定就是遍历数组a,然后b[i]等于这一排的数连乘,但不包括a[i],使用暴力肯定是超时的。
我们可以拆分问题,通过i把数组分割成左右两部分,先遍历一次a数组,把左半部分对应每个b[i]进行连乘,然后在进行一次a数组遍历,从倒数第二个开始反向遍历,补上b[i]的右半部分,我们只需要两次拆分遍历便可完成连乘数组b的构建。可以看K神的分析图来理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int [] constructArr(int [] a) { if (a.length == 0 ) return new int [0 ]; int [] b = new int [a.length]; int temp = 1 ; b[0 ] = 1 ; for (int i = 1 ; i < a.length; i++){ b[i] = b[i-1 ] * a[i-1 ]; } for (int i = a.length-2 ; i >= 0 ; i--){ temp *= a[i+1 ]; b[i] *= temp; } return b; } }
把字符串转换成整数 题目:https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/
这道题确实有思路,也写出来了90%,就在最后拉了跨,超过long的范围的值确实有点不会处理,但tm怎么会有”+-2”、”-5-“、”-13+5”这种测试用例的。。。。。这几个用例真nm绝了。
自己写的有缺陷版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int strToInt (String str) { str = str.trim(); StringBuilder temp = new StringBuilder(); for (int i = 0 ; i < str.length(); i++){ char c = str.charAt(i); if (c == ' ' || c == '+' ) continue ; if (c == '-' ) temp.append('-' ); else if (c >= 48 && c <= 57 ) temp.append(c); else break ; } str = temp.toString(); long ans = 0 , cur = 1 ; for (int i = str.length() - 1 ; i >= 0 ; i--){ char c = str.charAt(i); if (c == '-' ){ ans = 0 - ans; break ; } ans += (c-48 ) * cur; cur *= 10 ; } if (ans < 0 && ans < Integer.MIN_VALUE) return Integer.MIN_VALUE; if (ans > Integer.MAX_VALUE) return Integer.MAX_VALUE; return (int )ans; } }
K神yyds,不多说啊,符号位和越界的处理都十分巧妙,五体投地了这下是。时间复杂度O(n),因为使用了trim()去空格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int strToInt (String str) { char [] c = str.trim().toCharArray(); if (c.length == 0 ) return 0 ; int ans = 0 , bndry = Integer.MAX_VALUE / 10 ; int i = 1 , sign = 1 ; if (c[0 ] == '-' ) sign = -1 ; else if (c[0 ] != '+' ) i = 0 ; for (int j = i; j < c.length; j++){ if (c[j] < '0' || c[j] > '9' ) break ; if (ans > bndry || ans == bndry && c[j] > '7' ) return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; ans = ans * 10 + (c[j] - '0' ); } return sign * ans; } }
k神究极优化版本,没有使用trim(),将时间复杂度降到O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int strToInt (String str) { int res = 0 , bndry = Integer.MAX_VALUE / 10 ; int i = 0 , sign = 1 , length = str.length(); if (length == 0 ) return 0 ; while (str.charAt(i) == ' ' ) if (++i == length) return 0 ; if (str.charAt(i) == '-' ) sign = -1 ; if (str.charAt(i) == '-' || str.charAt(i) == '+' ) i++; for (int j = i; j < length; j++) { if (str.charAt(j) < '0' || str.charAt(j) > '9' ) break ; if (res > bndry || res == bndry && str.charAt(j) > '7' ) return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; res = res * 10 + (str.charAt(j) - '0' ); } return sign * res; } }
二叉搜索树的最近公共祖先 题目:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-i-er-cha-sou-suo-shu-de-zui-jin-g-7/
自己的解法,我们充分应用二叉搜索树的特性,左小右大,若根结点刚好处于两结点中间,说明根结点就是最小公共结点,返回根结点即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { TreeNode ans; int min, max; public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { min = Math.min(p.val, q.val); max = Math.max(p.val, q.val); MinCommonNode(root, min, max); return ans; } private void MinCommonNode (TreeNode root, int min, int max) { if (root == null ) return ; if (root.val >= min && root.val <= max ){ ans = root; return ; } MinCommonNode(root.left, min, max); MinCommonNode(root.right, min, max); } }
K神优化:我们直接判断两个结点是否同时大于/小于根结点,若大于/小于则遍历右子树/左子树,否则就说明根结点处于二者中间,此时直接返回根结点即可,这里相当于转了一个弯,只判断结点处于同一侧子树的情况,那么其他情况一定是结点在根结点两侧,直接返回根结点即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (p.val > q.val) { TreeNode tmp = p; p = q; q = tmp; } while (root != null ) { if (root.val < p.val) root = root.right; else if (root.val > q.val) root = root.left; else break ; } return root; } } class Solution { public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); return root; } }
二叉树的最近公共祖先 题目:https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/
根结点的左右子树分别包含一个p、q结点,此时根结点就是位于正中间,即p、q最近公共祖先
如果p、q结点不是分布于根结点左右两侧,说明只分布于其中的一侧
位于左子树,返回递归左子树的结果即最近公共祖先
位于右子树,返回递归右子树的结果即最近公共祖先
如果两侧都没有,说明p、q并不在当前根结点下,返回空
这道题其实就是递归思路的运用,题解没看懂只能说还没理解清楚递归。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root == null ) return null ; if (root == p || root == q) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left != null && right != null ) return root; if (left != null ) return left; if (right != null ) return right; return null ; } }
K神优化版本,转换了一点点思路就优化了代码,是真的强。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left == null ) return right; if (right == null ) return left; return root; } }