目录
2. Longest Substring Without Repeating Characters
1. Two sums (1)
Sorted: 头尾指针,sum若大右指针左移,sum若小左指针右移,找到即返回index。注:在必定有解的情况下。时间复杂度 O(N), 空间复杂度 O(1)。
Unsorted:
1.1. 先遍历存哈希表,key存值value存index,然后再遍历从Hashmap中找sum - target(因有唯一解,故不必考虑去重,但在第二遍遍历时需要检查 map.get(complement) != i );
1.2. 仅遍历一遍,遍历时先在Hashmap里找sum - target,再存(key存值value存index)。因为先查找再存自己所以查找时不用检查是不是自己。 时间复杂度 O(N), 空间复杂度 O(N)。
C++的码:
2. Longest Substring Without Repeating Characters (3)
先异常检查不能忘。左右指针从0出发。循环中,右指针往右移动时,存储指向的character以及character在左右指针中间出现的次数。一旦其指向的character出现的次数大于一,则左指针开始循环向右移动同时更新character出现次数,直到右指针指向的character出现次数等于1。比较两指针中间string的长度后继续移动右指针。用Char array和Hashmap皆可。时间复杂度 O(N) (虽然双重循环,但两指针总共最多走2N的距离), 空间复杂度 O(N).
优化:储存的时候key是character而value是character的index。如此下来,若检查character时发现index不为null且在左右指针之间,则说明该character已经出现过。如此,左指针可直接跳到index + 1的位置,从而不必进行内循环。时间复杂度 O(N), 空间复杂度 O(N).
Java的码:
3. Container With Most Water (11)
先异常检查。左右指针一头一尾。在两指针相遇前,循环中寻找两边中的较短边,计算后向内移动最短边。此做法可以有解的大致原因是,一开始水槽的底边已经最长,要盛更多的水只能使之加高,而水槽的高度是由短边决定的。因此,只有改变较短边才有可能改变水槽的容量,从而寻找最大值。时间复杂度 O(N), 空间复杂度 O(1)。
Java的码:
public int maxArea(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
int left = 0;
int right = height.length - 1;
int shorterBar, tempVolume;
int volume = 0;
while (left < right) {
shorterBar = height[left] < height[right] ? left : right;
tempVolume = (right - left) * height[shorterBar];
volume = tempVolume > volume ? tempVolume : volume;
if (left == shorterBar) {
++left;
} else {
--right;
}
}
return volume;
}
4. 3Sum (15)
4.1 HashMap法:异常检查后,先排序以便最后去重。遍历后将每个数字和出现次数存入哈希表。第二次遍历时左指针index 0,右指针index 1开始,两层循环遍历每个2sum然后在hashmap查找是否存在第三个数。为了去重,需要continue过nums[左指针] == nums[左指针 - 1]的情况(记得左指针>0)、nums[右指针] == nums[右指针 - 1]的情况(但右指针 == 左指针 + 1的时候是不能跳过的,因为这种情况下两指针指的数相等,可以是一种解的可能情况)。算出第三个数需要的值后(target = 0 - nums[左] - nums[右])查找map中有没有,并且是不是不小于nums[右](此处是为了满足(nums[左] <= nums[右] <= target,这样可以节省检索一些重复的情况)。最后,如果有,检查这个target的出现次数是否满足。正常应该是最低一次,但如果target等于任何一个指针指向的数,他出现的次数都必须要加一,不然的话target就没有足够的次数满足我们的检索。最后返回List。时间复杂度 O(N^2)(排序算O(nlogn)),空间复杂度O(N)。
Java的码:
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length < 3) {
return result;
}
Arrays.sort(nums);
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; ++i) {
if (map.containsKey(nums[i])) {
map.compute(nums[i], (key, value) -> ++value);
} else {
map.put(nums[i], 1);
}
}
for (int i = 0; i < nums.length; ++i) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
for (int j = i + 1; j < nums.length; ++j) {
if (j != i + 1 && nums[j] == nums[j - 1]) continue;
int target = 0 - nums[i] - nums[j];
if (!map.containsKey(target) || target < nums[j]) continue;
int times = 1 + (target == nums[j] ? 1:0) + (target == nums[i] ? 1:0);
if (map.get(target) >= times) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(target);
result.add(list);
}
}
}
return result;
}
4.2 双指针法:异常检查后排序,外侧指针 i 遍历0到 length-2,然后左指针从i右边开始,右指针从末尾开始往中间移动。三个指针指向的数相加,大于0则右指针左移,小于0则左指针右移,直到左右指针相遇,则外侧指针右移一格,重复操作。时间复杂度: O(N^2),空间复杂度 O(1)。
注意点:1. 因为排过序了,所以若nums[i]>0,则可以直接结束程序;2. 去重。i > 0 && nums[i] == nums[i - 1] 时要continue过;3. 左右指针如果不是在初始位置的话也需要去重,与4.1中方法相同;4. 找到一组解后不能直接break,需要继续移动左右指针寻找其余解。
Java的码:
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length < 3) {
return result;
}
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; ++i) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i - 1]) continue;
int right = nums.length - 1;
int left = i + 1;
int target = 0 - nums[i];
int sum;
while (left < right) {
sum = nums[left] + nums[right];
if ((left != i + 1 && nums[left] == nums[left - 1]) || (sum < target)) {
++left;
} else if ((right < nums.length - 1 && nums[right] == nums[right + 1])
|| (sum > target)) {
--right;
} else {
List<Integer> list = new ArrayList<>();
list.add(nums[left++]);
list.add(nums[right--]);
list.add(nums[i]);
result.add(list);
}
}
}
return result;
}