核心思路:空间换时间+升维
数组
连续的内存空间,支持随机访问,时间复杂度 O(1)
插入、删除操作比较低效O(n)
链表
单链表、双向链表、循环链表、双向循环链表
更适合插入、删除操作频繁的场景,时间复杂度 O(1)
访问时遍历链表 ,平均情况时间复杂度为O(n)
跳表
空间换时间,多级索引来提高查询的效率,实现了基于链表的“二分查找”,是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度为O(nlogn)
283.移动零
解法一 双指针 (j始终记录下一个非零元素的位置)
public void moveZeroes(int[] nums){
int j = 0;
for(int i = 0 ;i < nums.length; ++i){
if (nums[i] != 0){
nums[j] = nums[i];
if(i!=j){
nums[i] = 0;
}
j++;
}
}
}
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
解法二 遇零交换 注意python交换的特殊用法
def moveZeroes(self , nums):
zero = 0
for i in xrange( len(nums) ):
if nums[i] != 0:
nums[i], nums[zero] = nums[zero], num[i]
zero += 1
11.盛最多水的容器
解法一 暴力求解 枚举O(n^2)
public int maxArea(int[] a){
int max = 0;
for(int i = 0;i <a.length -1; ++i){
for(int j = i+1; j<a.length; ++j){
int area = (j-i)*Math.min(a[i],a[j]);
max = Math.max(max,area);
}
}return max;
}
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解法二 从两边往中间筛选更高的(左右夹逼法)O(N)
public int maxArea(int[] a){
int max = 0;
for(int i = 0, j = a.length- 1 ; i <j ;){
int minHight = a[i] < a[j] ? a[i++] : a[j--];
int area = (j - i + 1)* minHight;
max = Math.max(max,area);
}return max;
}
70.爬楼梯
1:1
2:2
3:f(1)+f(2)
n : f(n)=f(n-1)+f(n-2)
不缓存中间变量,只保留最后三个值
def climbStairs(self, n):
if(n <= 2):return n
f1 , f2 , f3 = 1 , 2 , 3
for i in range(3, n+1):
f3 = f1 + f2
f1 = f2
f2 = f3
return f3
1.两数之和
解法一 枚举不同的下标O(n^2)
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] == target - nums[i]) {
return new int[] { i, j };
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
解法二 使用两次迭代。O(n)
在第一次迭代中,将每个元素的值和它的索引添加到表中。在第二次迭代中,检查每个元素所对应的目标元素(target - nums[i])是否存在于表中。
注意,该目标元素不能是 nums[i] 本身
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement) && map.get(complement) != i) {
return new int[] { i, map.get(complement) };
}
}
throw new IllegalArgumentException("No two sum solution");
}
解法三 一遍哈希表 O(n)
在进行迭代并将元素插入到表中的同时,我们回过头来检查表中是否已经存在当前元素所对应的目标元素。如果它存在,那我们已经找到了对应解,并立即将其返回。
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
15.三数之和
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解法一 暴力求解 三种循环 O(n^3)
解法二 hash表来记录 a, b , a+b = -c
解法三 左右下标推进;双指针动态消去无效解来优化效率
O(N^2):其中固定指针k循环复杂度 O(N),双指针 i,j 复杂度 O(N)。
python:
def threeSum(self, nums: [int]) -> [[int]]:
nums.sort()
res, k = [], 0
for k in range(len(nums) - 2):
if nums[k] > 0: break # 1. because of j > i > k.
if k > 0 and nums[k] == nums[k - 1]: continue # 2. skip the same `nums[k]`.
i, j = k + 1, len(nums) - 1
while i < j: # 3. double pointer
s = nums[k] + nums[i] + nums[j]
if s < 0:
i += 1
while i < j and nums[i] == nums[i - 1]: i += 1
elif s > 0:
j -= 1
while i < j and nums[j] == nums[j + 1]: j -= 1
else:
res.append([nums[k], nums[i], nums[j]])
i += 1
j -= 1
while i < j and nums[i] == nums[i - 1]: i += 1
while i < j and nums[j] == nums[j + 1]: j -= 1
return res
java:
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int k = 0; k < nums.length - 2; k++){
if(nums[k] > 0) break;
if(k > 0 && nums[k] == nums[k - 1]) continue;
int i = k + 1, j = nums.length - 1;
while(i < j){
int sum = nums[k] + nums[i] + nums[j];
if(sum < 0){
while(i < j && nums[i] == nums[++i]);
} else if (sum > 0) {
while(i < j && nums[j] == nums[--j]);
} else {
res.add(new ArrayList<Integer>(Arrays.asList(nums[k], nums[i], nums[j])));
while(i < j && nums[i] == nums[++i]);
while(i < j && nums[j] == nums[--j]);
}
}
}
return res;
}
1.排序,从小到大
2.固定最左数字的指针 k,双指针 i,j 分设在数组索引 (k, len(nums)) 两端
当i < j时循环计算s = nums[k] + nums[i] + nums[j]
当s < 0时,i += 1并跳过所有重复的nums[i];
当s > 0时,j -= 1并跳过所有重复的nums[j];
注:
(1)当 nums[k] > 0 时直接break跳出
(2)当 k > 0且nums[k] == nums[k - 1]时即跳过
环形链表
解法一:哈希表O(N)
检查一个结点此前是否被访问过来判断链表是否为环形链表
public boolean hasCycle(ListNode head) {
Set<ListNode> nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
解法二:快慢指针O(n)
java
public boolean hasCycle(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) return true;
}
return false;
}
python
def hasCycle(self, head):
fast, slow = head, head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow: return True
return False
空间复杂度:O(1),只使用了慢指针和快指针两个结点