携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
88. 合并两个有序数组 - 简单
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
题解:
我们可以声明三个指针:
pos指向nums1数组的最后位置,即合并后数组的最后一个位置;m指向nums1数组元素的最后一个位置,如例子中指向元素3n指向nums2数组元素的最后一个位置,如例子中指向元素6比较
m和n所指向元素的大小,将较大者放置到pos指针所指向的位置,同时移动pos指针和值较大者的指针例如:当 m = 2,n = 2 时,
nums1[m] = 3 < nums2[n] = 6,令nums1[pos] = 6,同时指针pos和n指针同时向前移动。❗ 需要注意,如果 nums1 提前遍历结束,我们仍需要遍历 nums2 数组;但是如果 nums2 提前遍历结束,我们就不需要继续遍历 nums1 数组了。
代码:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int pos = m-- + n-- -1;
while (m >= 0 && n >= 0) {
nums1[pos--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
}
while (n >= 0) {
nums1[pos--] = nums2[n--];
}
}
}
633. 平方数之和 - 简单
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 。
示例:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
题解:
❗❗ 这道题我们需要注意使用
long类型,因为在计算的过程中,使用到了乘法以及加法,如果使用int可能会有整形溢出的问题。我们令 , 令 , 然后根据 与 比较,决定是令 a 增加还是令 b 减少,最终确定是否有两个整数满足要求。
代码:
class Solution {
public boolean judgeSquareSum(int c) {
long a = 0, b = (long) Math.sqrt(c);
while (a <= b) {
long sum = a * a + b * b;
if (sum == c) {
return true;
}else if(sum < c) {
a++;
}else {
b--;
}
}
return false;
}
}
680. 验证回文字符串 Ⅱ - 简单
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例:
输入: s = "aba"
输出: true
输入: s = "abca"
输出: true
解释: 你可以删除c字符。
题解:
我们可以定义两个指针
l和r分别指向字符串s的左边和右边。如果指针
l和指针r所指向的字符相等,同时移动指针l和r。如果不相等,看看将这个不相等的字符删除掉后是否是回文串,即将
l移动一个位置。如果依然不是回文串,再看看右边删除掉一个字符后是否为回文串。
代码:
class Solution {
public boolean validPalindrome(String s) {
int l = 0, r = s.length()-1;
while (l <= r) {
if (s.charAt(l) == s.charAt(r)) {
l++;
r--;
}else {
return validPalindrome(s, l+1, r) || validPalindrome(s, l, r-1);
}
}
return true;
}
public boolean validPalindrome(String s, int l, int r) {
while (l <= r) {
if (s.charAt(l) != s.charAt(r)) {
return false;
}
l++;
r--;
}
return true;
}
}
167. 两数之和 II - 输入有序数组 - 中等
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
题解:
这道题还是相对简单的,由于数组有序,我们可以定义两个指针:
- 指针
l指向数组左边界- 指针
r指向数组右边界如果两个指针所指元素之和大于
target,表示 r 所指元素太大了,我们移动r指针;反之,如果元素之和小于target,我们移动左指针l;这样子,让元素之和不断接近target,直到找到对应的元素或者左指针大于等于右指针(表示数组中没有满足要求的元素)。
代码:
class Solution {
public int[] twoSum(int[] numbers, int target) {
int l = 0, r = numbers.length-1;
while (l < r) {
int sum = numbers[l] + numbers[r];
if (sum == target) {
return new int[]{l+1,r+1};
}else if (sum < target) {
l++;
}else {
r--;
}
}
// 数组中没有满足要求的元素
return null;
}
}
524. 通过删除字母匹配到字典里最长单词 - 中等
给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。
示例:
输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"
题解:
我们可以遍历 dictionary 中的每个单词
word,在遍历过程中,我们定义两个指针:
- 指针
i指向字符串s,指向第一个字符- 指针
j指向单词word如果指针
i和j所指字符相同,那么同时移动两个指针,否则,只移动指针i,表示字符串s需要删除字符。如果指针
j能够遍历完单词word,则表示字符串s可以通过删除字符匹配该单词word。❗❗ 但是需要注意,先前找到的单词长度是否比该单词小,如果是则更新为当前单词;如果长度相等,则比较字母序。
代码:
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
String ans = "";
for (String word : dictionary) {
int i = 0, j = 0;
while (i < s.length() && j < word.length()) {
if (s.charAt(i) == word.charAt(j)) {
j++;
}
i++;
}
if (j == word.length()) {
if (word.length() > ans.length()) {
ans = word;
} else if (word.length() == ans.length()) {
ans = word.compareTo(ans) > 0 ? ans : word;
}
}
}
return ans;
}
}
142. 环形链表 II - 中等
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
题解:
这道题可以使用快慢指针。
定义两个指针 fast 和 slow,fast 向后移动两步,slow 移动一步。
如果 fast 为空,说明没有环。否则,当 fast 和 slow 相等时,slow 回到链表起点,随后两个指针以相同速度移动,直到两个指针所指元素相同,这个元素即为环的入口。
合理性证明:
当 fast 和 slow 第一次相遇时,fast 所走过的路程为和 slow 所走过的路程的关系为 ,
化简,得
从上面公式可以看出,当 fast 和 slow 相遇后,slow 和 fast 以同样的速度重新走一遍路程 a,就相当于 fast 再走 (n-1) 环(此时又回到第一次相遇的点)后,再走 c 的路程,刚好是环的入口,也刚好和 slow 再次相遇。
💡 本题也可以使用哈希表完成
代码:
class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
do {
// 如果 fast 指针能指向 null,说明链表中不存在环
if (fast == null || fast.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
}while (fast != slow);
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
76. 最小覆盖子串 - 困难
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
- 如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
输入:s = "a", t = "a"
输出:"a"
题解:
这道题可以使用滑动窗口来解决,同样也是双指针。
我们利用滑动窗口寻找覆盖子串,如果找到一个覆盖子串,我们缩小滑动窗口直到该子串不覆盖字符串
t,再继续扩大窗口,如此反复,直到找到最小的覆盖子串首先,我们定义两个数组:
- char 数组
chars,用来记录字符串t中各个字符的出现次数- boolean 数组
flag,用来记录字符串t中需要哪些字符,即必须字符随后,遍历字符串
s,我们需要两个指针,指针l作为字符串s上的窗口的左边界,指针r作为窗口的右边界。同时,还需要一个计数器count,用来记录当前窗口内是否已经覆盖了字符串t。⭐ 当
count的值与字符串t长度相等时,即窗口已经覆盖了字符串t。我们移动指针
r,同时维护chars数组,将指针r所指字符c的次数减一。如果字符
c是必须字符,并且窗口内该字符已经满足了最小要求(即 字符串t中该字符的出现个数),那么count加一。不断移动右边界指针
r。直到
count的值与字符串t长度相等时,我们开始移动左边界l。同时更新最小覆盖子串的长度minLen和该覆盖子串的起始位置minL。并且指针
l所指的字符lc的需要个数重新加一。如果该字符
lc所指字符是必须字符,并且该字符在滑动窗口之外(即左边界外),同时所需个数大于零,那么计数器count减一,我们需要重新移动右边界,找出另一个覆盖子串,看是否长度更小并更新。
代码:
public String minWindow(String s, String t) {
int sLen = s.length(), tLen = t.length();
// 记录 所需字符的个数
int[] chars = new int[128];
// 记录那些字母是必须的
boolean[] flag = new boolean[128];
// 初始化 chars 和 flag
for (int i = 0; i < tLen; ++i) {
char c = t.charAt(i);
chars[c]++;
flag[c] = true;
}
// count 用来记录子串是否已经满足要求, minL 记录最小子串的起始索引
int count = 0, l = 0, minL = 0, minLen = s.length() + 1;
// 移动右窗口
for (int r = 0; r < sLen; ++r) {
// 窗口向右移动,所需字符个数 减 1,这里对比上面的解法,对于不需要的字符也会多了这步操作
char c = s.charAt(r);
chars[c]--;
// 如果 字母是必须的,并且所需字符仍为满足,为0 表示刚好满足个数要求
if (flag[c] && chars[c] >= 0) {
count++;
}
// 如果当前窗口已经 满足要求
while (count == tLen) {
// 更新最小子串长度
if (r-l+1 < minLen) {
minLen = r-l+1;
minL = l;
}
// 左窗口向右移动,字母个数需要增加
char lc = s.charAt(l);
chars[lc]++;
// 如果字符是必须要的,此时移动了左窗口,该字符个数需要增加,如果 > 0 子串将不满足要求,count -1
if (flag[lc] && chars[lc] > 0) {
count--;
}
l++;
}
}
return minLen > sLen ? "" : s.substring(minL, minL + minLen);
}