1143.最长公共子序列 视频教学:https://www.bilibili.com/video/BV14A411v7mP
基本思路是转化为二维数组
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 int longestCommonSubsequence (String text1, String text2) { int n=text1.length(),m=text2.length(); char [] s1=text1.toCharArray(),s2=text2.toCharArray(); int [][] f = new int [n+1 ][m+1 ]; for (int i=1 ;i<n+1 ;i++){ for (int j=1 ;j<m+1 ;j++){ if (s1[i-1 ] == s2[j-1 ]) f[i][j]=f[i-1 ][j-1 ]+1 ; else f[i][j]=Math.max(f[i-1 ][j],f[i][j-1 ]); } } return f[n][m]; } }
208.实现Trie(构造前缀树) 参考1:https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/trie-tree-de-shi-xian-gua-he-chu-xue-zhe-by-huwt/
参考2:https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/gong-shui-san-xie-yi-ti-shuang-jie-er-we-esm9/
前缀树思想还是好理解的,注意看怎么实现,它的结点构造是包含一个结点类型的数组,这是关键,我一开始没搞明白这个数组,所以有几个地方卡住了(next.node[ch] = new TrieNode();就是这句!),建议插入和查两个功能对比着看好理解,我看插入还有点没想明白,看到查找就懂了。
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 55 56 57 58 59 class Trie { class TrieNode { boolean isEnd; TrieNode[] node = new TrieNode[26 ]; } TrieNode root; public Trie () { root = new TrieNode(); } public void insert (String word) { TrieNode next = root; for (int i=0 ;i<word.length();i++){ int ch = word.charAt(i)-'a' ; if (next.node[ch] == null ) next.node[ch] = new TrieNode(); next=next.node[ch]; } next.isEnd=true ; } public boolean search (String word) { TrieNode next = root; for (int i=0 ;i<word.length();i++){ int ch = word.charAt(i)-'a' ; if (next.node[ch] == null ) return false ; next=next.node[ch]; } return next.isEnd; } public boolean startsWith (String prefix) { TrieNode next = root; for (int i=0 ;i<prefix.length();i++){ int ch=prefix.charAt(i)-'a' ; if (next.node[ch] == null ) return false ; next=next.node[ch]; } return true ; } }
5773.插入后的最大值 5773.插入后的最大值。周赛碰到的,自己写的时候考虑不周全,首尾添加的情况老是出问题,学习一下别人的写法。
题目:https://leetcode-cn.com/problems/maximum-value-after-insertion/
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 String maxValue (String n, int x) { StringBuilder sb = new StringBuilder(); if (n.charAt(0 ) == '-' ){ for (int i=1 ;i<n.length();i++){ if (n.charAt(i) - 48 > x){ sb.append(n.substring(0 ,i)).append(x).append(n.substring(i)); return sb.toString(); } } }else { for (int i=0 ;i<n.length();i++){ if (x > n.charAt(i) - 48 ){ sb.append(n.substring(0 ,i)).append(x).append(n.substring(i)); return sb.toString(); } } } return n + x; } }
15.三数之和 题目:https://leetcode-cn.com/problems/3sum/
这道三数之和也有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 class Solution { public List<List<Integer>> threeSum(int [] nums) { Arrays.sort(nums); List<List<Integer>> ans = new ArrayList<>(); for (int a = 0 ; a < nums.length - 2 ; a++){ if (nums[a] > 0 ) return ans; if (a > 0 && nums[a] == nums[a-1 ]) continue ; int b = a + 1 , c = nums.length - 1 ; while (b < c){ int sum = nums[a] + nums[b] + nums[c]; if (sum < 0 ){ while (b < c && nums[b] == nums[++b]); } else if (sum > 0 ){ while (b < c && nums[c] == nums[--c]); }else { List<Integer> temp= new ArrayList<>(); temp.add(nums[a]); temp.add(nums[b]); temp.add(nums[c]); ans.add(temp); while (b < c && nums[b] == nums[++b]); while (b < c && nums[c] == nums[--c]); } } } return ans; } }
5.最长回文子串 题目:https://leetcode-cn.com/problems/longest-palindromic-substring/
一般的算法都需要O(n^2),我们就学习一下动态规划。
这里转移方程是:
字符串左右边界不等,一定不是回文串,记false
字符串左右相等,若其长度<=3说明一定是回文串,记true
字符串左右相等且长度>3,根据其内部子串是否为回文串来判断
然后如果现在的子串是回文串且长度最大就更新最长回文子串的判断长度与起始位,方便后续截取字符串进行输出。
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 longestPalindrome (String s) { int length = s.length(); if (length < 2 ) return s; int max = 1 , left = 0 ; boolean [][] dp = new boolean [length][length]; for (int i = 0 ; i < length; i++) dp[i][i] = true ; char [] str = s.toCharArray(); for (int len = 2 ; len <= length; len++){ for (int l = 0 ; l < length; l++){ int r = len + l - 1 ; if (r >= length) break ; if (str[l] != str[r]) dp[l][r] = false ; else if (r - l + 1 <= 3 ) dp[l][r] = true ; else dp[l][r] = dp[l+1 ][r-1 ]; if (dp[l][r] && r - l + 1 > max){ max = r - l + 1 ; left = l; } } } return s.substring(left, left + max); } }
19.删除链表的倒数第N个结点(快慢指针) 题目:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
经典快慢指针,一开始没思路看到评论说快慢就想到解法了,知道快慢指针后就很好做了,一次遍历即可得到答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { ListNode l = new ListNode(0 ), r = head; l.next = head; for (int i = n; n > 1 ; n--){ r = r.next; } while (r.next != null ){ l = l.next; r = r.next; } if (l.next == head) return head.next; l.next = l.next.next; return head; } }
3.无重复字符的最长子串 题目:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
典型的滑动窗口题目,我们注意重复值和左右边界的关系即可解题。当加入字符在窗口中没有重复值时再加入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int lengthOfLongestSubstring (String s) { if (s.length() <=1 ) return s.length(); Set<Character> set = new HashSet<>(); int l = 0 , max = 0 ; for (int r = 0 ; r < s.length(); r++){ while ( set.contains(s.charAt(r)) ){ set.remove(s.charAt(l++)); } set.add(s.charAt(r)); max = Math.max(r - l + 1 , max); } return max; } }
64.最小路径和 题目:https://leetcode-cn.com/problems/minimum-path-sum/
刚看到题就知道普通办法行不通,然后评论提到动态规划马上就懂了,就是一个普通的动态规划题目,比较简单。注意m是行、n是列,也就是m是竖着的长度,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 minPathSum (int [][] grid) { int m = grid.length, n = grid[0 ].length; int [][] dp = new int [m][n]; dp[0 ][0 ] = grid[0 ][0 ]; for (int i = 1 ; i < m; i++) dp[i][0 ] = dp[i-1 ][0 ] + grid[i][0 ]; for (int i = 1 ; i < n; i++) dp[0 ][i] = dp[0 ][i-1 ] + grid[0 ][i]; for (int i = 1 ; i < m; i++){ for (int j = 1 ; j < n; j++){ dp[i][j] = Math.min(dp[i-1 ][j], dp[i][j-1 ]) + grid[i][j]; } } return dp[m-1 ][n-1 ]; } }
300.最长递增子序列 题目:https://leetcode-cn.com/problems/longest-increasing-subsequence/
挺好理解的,但不是最优解法,今日摸鱼了,注意dp[n]的意义,即数组0~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 lengthOfLIS (int [] nums) { if (nums.length == 0 ) return 0 ; int [] dp = new int [nums.length]; int ans = 0 ; Arrays.fill(dp, 1 ); for (int i = 0 ; i < nums.length; i++){ for (int j = 0 ; j < i; j++){ if (nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1 ); } ans = Math.max(ans, dp[i]); } return ans; } }
2.两数相加 题目:https://leetcode-cn.com/problems/add-two-numbers/
一开始还想用辅助栈来把两个链表的数取出来相加,随后再存入到新链表,观察了一会发现可以直接对两个原链表操作,这样效率肯定更高,没想到直接做出了标准答案,看来最近的刷题还是有用的。
当然这里可以发现代码没有完全实现复用,评论区有优化版本,不过我的思路是正确的,让代码进一步优化还要学习。
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 class Solution { ListNode head = new ListNode(0 ); ListNode ans = head; int num, ten = 0 ; public ListNode addTwoNumbers (ListNode l1, ListNode l2) { while (l1 != null && l2 != null ){ num = l1.val + l2.val + ten; head.next = new ListNode(num % 10 ); ten = num / 10 ; head = head.next; l1 = l1.next; l2 = l2.next; } loop(l1); loop(l2); if (ten != 0 ){ head.next = new ListNode(ten); head = head.next; } return ans.next; } void loop (ListNode node) { while (node != null ){ num = node.val + ten; head.next = new ListNode(num % 10 ); ten = num / 10 ; head = head.next; node = node.next; } } }
141.环形链表 题目:https://leetcode-cn.com/problems/linked-list-cycle/
简单做法是走过一个链表结点就加入Set,若加入失败则说明有环。
复杂一点效率高就是使用快慢指针,快慢指针重合就说明有环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Solution { public boolean hasCycle (ListNode head) { if (head == null || head.next == null ) return false ; ListNode quick = head.next; ListNode slow = head; while (quick != slow){ if (quick == null || quick.next == null ) return false ; quick = quick.next.next; slow = slow.next; } return true ; } }
62.不同路径 题目:https://leetcode-cn.com/problems/unique-paths/
经典动态规划,初始化两条边然后dp即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int uniquePaths (int m, int n) { int [][] dp = new int [m][n]; dp[0 ][0 ] = 1 ; for (int i = 1 ; i < m; i++) dp[i][0 ] = dp[i - 1 ][0 ]; for (int i = 1 ; i < n; i++) dp[0 ][i] = dp[0 ][i - 1 ]; for (int i = 1 ; i < m; i++) for (int j = 1 ; j < n; j++) dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ]; return dp[m - 1 ][n - 1 ]; } }
91.解码方法 题目:https://leetcode-cn.com/problems/decode-ways/
其实这题是一个常规动态规划,理清楚思路后不难理解,笔试的时候死活没想到转移方程,新加入字符的编码次数与自身和前一位的组合相关,通过这个得到转移方程即可。
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 int numDecodings (String s) { s = " " + s; int len = s.length(); int [] dp = new int [len]; dp[0 ] = 1 ; for (int i = 1 ; i < len; i++){ int one = s.charAt(i); int two = (s.charAt(i - 1 ) - '0' ) * 10 + s.charAt(i) - '0' ; if (one >= '1' && one <= '9' ) dp[i] = dp[i - 1 ]; if (two >= 10 && two <= 26 ) dp[i] += dp[i - 2 ]; } return dp[len - 1 ]; } }
202.快乐数 题目:https://leetcode-cn.com/problems/happy-number/
确实是简单题,我们只要发现当前数存在于一个循环中,那么他一定不是快乐数,然后若当前数等于1就是快乐数,使用递归轻松解决。判断循环有多种方法,这里我是将数存放到set中,添加失败就说明有循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { Set<Integer> set = new HashSet<>(); public boolean isHappy (int n) { int sum = 0 ; int cur = 0 ; while (n != 0 ){ cur = n % 10 ; sum += cur * cur; n /= 10 ; } if (sum == 1 ) return true ; if (!set.add(sum)) return false ; return isHappy(sum); } }
42.接雨水 题目:https://leetcode-cn.com/problems/trapping-rain-water/
不要看到困难就怕了。其实用动态规划做很好想明白的,如果我们只是单一的接雨水,也就是有递减就算接水,那么题目就很简单。但是这里需要考虑一个区间问题,也就是接雨水处于一个区间内,需要把雨水包住,形成一个盆地,就是要先递减后递增的区间,比如看题目的示例,虽然最大高度是3,但没有其他柱子可以与3形成盆地接水,我们最终用来判断接水的是较小的最大高度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 class Solution { public int trap (int [] height) { int len = height.length; int [] left_increase = new int [len]; left_increase[0 ] = height[0 ]; for (int i = 1 ; i < len; i++) left_increase[i] = Math.max(left_increase[i - 1 ], height[i]); int [] right_increase = new int [len]; right_increase[len - 1 ] = height[len - 1 ]; for (int i = len - 2 ; i >= 0 ; i--) right_increase[i] = Math.max(right_increase[i + 1 ], height[i]); int ans = 0 ; for (int i = 0 ; i < len; i++) ans += Math.min(left_increase[i], right_increase[i]) - height[i]; return ans; } }