两数之和 LeetCode 1.两数之和
问题
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
哈希法
使用哈希Map,存储格式为<数值, 索引>。用哨兵i遍历数组,同时查找哈希表中是否含有target-nums[i],如果有,则返回答案。如果没有,则把当前遍历的元素<nums[i], i>存入哈希,继续遍历下一个元素即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public int [] twoSum(int [] nums, int target) { Map<Integer, Integer> map = new HashMap <>(); for (int sentinel = 0 ; sentinel < nums.length; sentinel++) { if (map.containsKey(target - nums[sentinel])) { return new int [] {sentinel, map.get(target - nums[sentinel])}; } map.put(nums[sentinel], sentinel); } return new int [0 ]; }
总结反思
使用哈希可以快速存储目标元素,减少查询某元素时间复杂度开销
两数相加 LeetCode 2.两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
博主解法:暴力解法
设置两个指针指向两个链表表头,变量ans用于存储两个指针元素与ans之和,取出元素的个位作为新节点,然后将十位数字存储到ans中,两个指针右移,开始下一次循环,计算两个指针元素以及ans的和,重复上述步骤,当一方指针走到尽头时,将ans与另一方元素求和得到新的ans,继续取出个位和十位,如果十位不是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 public ListNode addTwoNumbers (ListNode l1, ListNode l2) { ListNode p = l1, q = l2, s = null ; int ans = 0 ; ListNode h = new ListNode (0 ); s = h; while (p != null && q != null ) { ans += p.val + q.val; ListNode node = new ListNode (ans % 10 ); ans = ans / 10 ; s.next = node; s = node; p = p.next; q = q.next; } while (p != null ) { ans += p.val; ListNode node = new ListNode (ans % 10 ); ans = ans / 10 ; s.next = node; s = node; p = p.next; } while (q != null ) { ans += q.val; ListNode node = new ListNode (ans % 10 ); ans = ans / 10 ; s.next = node; s = node; q = q.next; } if (ans != 0 ) { ListNode node = new ListNode (ans); s.next = node; s = node; } return h.next; }
模拟
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 public ListNode addTwoNumbers (ListNode l1, ListNode l2) { ListNode head = null , tail = null ; int carry = 0 ; while (l1 != null || l2 != null ) { int n1 = l1 != null ? l1.val : 0 ; int n2 = l2 != null ? l2.val : 0 ; int sum = n1 + n2 + carry; if (head == null ) { head = tail = new ListNode (sum % 10 ); } else { tail.next = new ListNode (sum % 10 ); tail = tail.next; } carry = sum / 10 ; if (l1 != null ) { l1 = l1.next; } if (l2 != null ) { l2 = l2.next; } } if (carry > 0 ) { tail.next = new ListNode (carry); } return head; }
无重复字符的最长子串 LeetCode 3.无重复字符的最长子串
问题
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
解法:滑动窗口
使用两个指针表示字符串中的某个子串(或窗口)的左右边界,其中左指针代表着上文中「枚举子串的起始位置」
在每一步的操作中,我们会将左指针向右移动一格,表示 我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。在移动结束后,这个子串就对应着 以左指针开始的,不包含重复字符的最长子串。我们记录下这个子串的长度;
在枚举结束后,我们找到的最长的子串的长度即为答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public int lengthOfLongestSubstring (String s) { Set<Character> set = new HashSet <>(); int n = s.length(); int right = -1 , ans = 0 ; for (int left = 0 ; left < n; left++) { if (left != 0 ) { set.remove(s.charAt(left - 1 )); } while (right + 1 < n && !set.contains(s.charAt(right + 1 ))) { set.add(s.charAt(right + 1 )); ++right; } ans = Math.max(ans, right - left + 1 ); } return ans; }
寻找两个正序数组的中位数 LeetCode 4.寻找两个正序数组的中位数
问题
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
算法思想
最长回文子串 LeetCode 5.最长回文子串
问题
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
枚举法 双重for-loop枚举出所有可能性,然后寻找最优解
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 public String longestPalindrome (String s) { if (s.length() < 2 ) { return s; } int maxLen = 1 ; int init = 0 ; char [] ch = s.toCharArray(); for (int i = 0 ; i < s.length() -1 ;; ++i) { for (int j = i + 1 ; j < s.length(); ++j) { if (j - i + 1 > maxLen && validPalindrome(chars, i, j)) { maxLen = j - i + 1 ; init = i; } } return s.substring(init, init + maxLen); } } private boolean validPalindrome (char [] ch, int left, int right) { while (left < right) { if (ch[left] != ch[right]) { return false ; } left++; right--; } return true ; }
动态规划 对于一个子串而言,如果它是回文串,并且长度大于2 ,那么将它首尾的两个字母去除之后,它仍然是个回文串。用 $P(i,j)$ 表示字符串 $s$ 的第 $i$ 到 $j$ 个字母组成的串是否为回文串。
[其他情况] :
$s[i,j]$ 本身不是一个回文串
$i>j$ ,此时 $s[i,j]$ 本身不合法
因此,dp公式为 $P(i,j)=P(i+1,j-1)∧(S_i==S_j)$。即只有s[i+1:j-1]是回文串,并且s的第i和j个字母相等时,s[i:j]才会是回文串。 上文的所有讨论是建立在子串长度大于 2 的前提之上的,我们还需要考虑动态规划中的边界条件,即子串的长度为 1 或 2。
对于长度为1的子串,显然是个回文串;
对于长度为2的子串,仅需要两个字母相同,就是一个回文串 因此加上dp边界:
根据这个思路,我们就可以完成动态规划了,最终的答案即为所有 P(i,j)=true中j-i+1的最大值。注意:在dp公式中,我们是从长度较短的字符串向长度较长的字符串进行转移 的,因此一定要注意表格的计算顺序。
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 public String longestPalindrome (String s) { if (s.length() < 2 ) { return s; } int maxLen = 1 ; int begin = 0 ; boolean [][] dp = new boolean [s.length()][s.length()]; for (int i = 0 ; i < s.length(); ++i) { dp[i][i] = true ; } char [] chars = s.toCharArray(); for (int subLen = 2 ; subLen <= s.length(); subLen++) { for (int left = 0 ; left < s.length(); left++) { int right = subLen + left - 1 ; if (right >= s.length()) { break ; } if (chars[left] != chars[right]) { dp[left][right] = false ; } else { if (right - left < 3 ) { dp[left][right] = true ; } else { dp[left][right] = dp[left + 1 ][right - 1 ]; } } if (dp[left][right] && right - left + 1 > maxLen) { maxLen = right - left + 1 ; begin = left; } } } return s.substring(begin, begin + maxLen); }
中心扩散法
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 public String longestPalindrome (String s) { if (s == null || s.length() < 1 ) { return "" ; } int start = 0 , end = 0 ; for (int i = 0 ; i < s.length(); i++) { int len1 = expandAroundCounter(s, i, i); int len2 = expandAroundCounter(s, i, i + 1 ); int curLen = Math.max(len1, len2); if (curLen > end - start) { start = i - (curLen - 1 ) / 2 ; end = i + curLen / 2 ; } } return s.substring(start, end + 1 ); } public int expandAroundCounter (String s, int left, int right) { while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { --left; ++right; } return right - left - 1 ; }
Manacher 具体请看 数据结构-11-Manacher
N 字形变换 LeetCode 6.N 字形变换
问题
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。