Code 142:环形链表 II
描述:判断链表是否有环,有则返回入环节点,无则返回空。
思路:这个题目其实是利用快慢指针的的遍历优势,如果链表是的无环,那么链表在遍历过程中,永远不会走空节点,如果有环,那么在这么遍历过程中,快指针与慢指针一定是会相遇的,那么就可以在遍历的过程中,根据快指针的进行判断,当快指针为空的时候,说明整个链表是无环的,如果不为空,说明链表一定有环,在这种情况下,有环链表,就在相遇的时候,让快指针或者慢指针回到头结点,一步一步的遍历下去,相遇的时候,这个节点就是入环的第一个节点
Java
public ListNode detectCycle(ListNode head) {
// 快慢指针
if (head == null || head.next == null) {
return null;
}
ListNode slow = head.next;
ListNode fast = head.next.next;
while (slow != fast) {
slow = slow.next;
if (fast == null || fast.next == null) {
return null;
}
fast = fast.next.next;
}
// 能跳出来说明有环
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
Code 148:排序链表
描述:利用归并排序实现空间复杂度 O(1)(非严格,还有递归的空间消耗)的做法,这里找中点,其实找上中点或者下中点都无所谓,主要是能将链表的进行拆分重建,利用归并排序的特性,将链表重组成有序的链表
Java
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
return process(head, null);
}
// 利用归并排序的特性 将整个链表修改成有序的链表
// 这个函数的含义是 在链表的范围内进行归并排序 并且返回经过归并排序的之后的新的头结点
public ListNode process(ListNode head, ListNode tail) {
if (head == tail || head.next == null) {
return head;
}
// 找到中点 从head到tail的终点 这点是上中点或者是下中点不是很重要
ListNode slow = head;
ListNode fast = head.next.next;
// 快慢指针 快指针一次走两步 慢指针一次走一步 知道快指针来到了最后一个位置
while (fast != tail) {
slow = slow.next;
if (fast == null || fast.next == null) {
break;
}
fast = fast.next.next;
}
// 中点
// 切断左右两段链表
ListNode mid = slow.next;
slow.next = null;
// 利用归并排序的特性对链表进行排序
ListNode l = process(head, null);
ListNode r = process(mid, null);
// 合并左右两侧链表
return merge(l, r);
}
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while (head1 != null && head2 != null) {
if (head1.val <= head2.val) {
cur.next = head1;
head1 = head1.next;
} else {
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
if (head1 != null) {
cur.next = head1;
}
if (head2 != null) {
cur.next = head2;
}
return dummyHead.next;
}
Code 152:乘积最大子数组
关联标注:思路参考了 Code 053 (和最大的子数组),即以当前位置作为开头/结尾的局部最优思想。
Java
// 思路1:暴力枚举
public int maxProduct(int[] nums) {
// 必须以i位置开头
int n = nums.length;
int ans = nums[0];
for (int i = n - 1; i >= 0; i--) {
int curMul = nums[i];
int curMax = curMul;
for (int j = i + 1; j < n; j++) {
curMul *= nums[j];
if (curMul > curMax) {
curMax = curMul;
}
}
ans = Math.max(ans, curMax);
}
return ans;
}
// 思路2:使用辅助数组
public int maxProduct2(int[] nums) {
int n = nums.length;
int[] max = new int[n];
int[] min = new int[n];
max[n - 1] = nums[n - 1];
min[n - 1] = nums[n - 1];
for (int i = n - 2; i >= 0; i--) {
max[i] = Math.max(nums[i], Math.max(min[i + 1] * nums[i], max[i + 1] * nums[i]));
min[i] = Math.min(nums[i], Math.min(min[i + 1] * nums[i], max[i + 1] * nums[i]));
}
int ans = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
if (max[i] > ans) {
ans = max[i];
}
}
return ans;
}
// 思路3:减少一次遍历
public int maxProduct3(int[] nums) {
int n = nums.length;
int[] max = new int[n];
int[] min = new int[n];
max[n - 1] = nums[n - 1];
min[n - 1] = nums[n - 1];
int ans = nums[n-1];
for (int i = n - 2; i >= 0; i--) {
max[i] = Math.max(nums[i], Math.max(min[i + 1] * nums[i], max[i + 1] * nums[i]));
min[i] = Math.min(nums[i], Math.min(min[i + 1] * nums[i], max[i + 1] * nums[i]));
ans = Math.max(ans, max[i]);
}
return ans;
}
// 思路4:压缩空间做法
public int maxProduct4(int[] nums) {
int n = nums.length;
int max = nums[n - 1], min = nums[n - 1];
int ans = nums[n - 1];
for (int i = n - 2; i >= 0; i--) {
int mx = max;
max = Math.max(nums[i], Math.max(max * nums[i], min * nums[i]));
min = Math.min(nums[i], Math.min(mx * nums[i], min * nums[i]));
ans = Math.max(ans, max);
}
return ans;
}
Code 153:寻找旋转排序数组中的最小值
描述:要求 O(log n)时间复杂度。
Java
// 方法1:与最后一个数 nums[n-1] 比较
public int findMin(int[] nums) {
int n = nums.length;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = (left + right) >>> 1;
if (nums[mid] <= nums[n - 1]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return nums[left];
}
// 方法2:与第一个数 nums[0] 比较
public int findMin2(int[] nums) {
int n = nums.length;
int left = 0;
int num = nums[0];
int right = n - 1;
while (left <= right) {
int mid = (left + right) >>> 1;
if (nums[mid] >= num) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left == n ? nums[0] : nums[left];
}
// 方法3:与 nums[0] 比较并提前剔除单调递增场景
public int findMin3(int[] nums) {
int n = nums.length;
int left = 0;
int num = nums[0];
int right = n - 1;
if(nums[right] >= nums[left]) {
return nums[left];
}
while(left <= right) {
int mid = left + ((right - left) >> 1);
if(nums[mid] >= num) {
left = mid +1;
}else{
right = mid - 1;
}
}
return nums[left];
}
Code 160:相交链表
描述:寻找两个链表相交的起始节点。
Java
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int n1 = 0;
ListNode cur1 = headA;
while (cur1 != null) {
n1++;
cur1 = cur1.next;
}
ListNode cur2 = headB;
while (cur2 != null) {
n1--;
cur2 = cur2.next;
}
if (n1 > 0) {
cur1 = headA;
cur2 = headB;
} else {
cur1 = headB;
cur2 = headA;
}
n1 = Math.abs(n1);
while (n1-- > 0) {
cur1 = cur1.next;
}
while (cur1 != null && cur2 != null) {
if (cur1 == cur2) {
return cur1;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
return null;
}
Code 169:多数元素
描述:寻找数组中出现次数大于 n/2 的元素。
Java
// 思路1:哈希表统计
public int majorityElement1(int[] nums) {
Map<Integer, Integer> map = new java.util.HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
if (map.get(nums[i]) > nums.length / 2) {
return nums[i];
}
}
return -1;
}
// 思路2:排序法
public int majorityElement2(int[] nums) {
java.util.Arrays.sort(nums);
return nums[nums.length / 2];
}
// 思路3:摩尔投票法 (Boyer-Moore)
public int majorityElement3(int[] nums) {
int times = 0;
int ans = 0;
for (int i = 0; i < nums.length; i++) {
if (times == 0) {
ans = nums[i];
times++;
} else if (nums[i] == ans) {
times++;
} else if (nums[i] != ans) {
times--;
}
}
return ans;
}
Code 189:轮转数组
描述:将数组中的元素向右轮转 k个位置。
Java
// 思路1:直接计算位置 + 辅助哈希表
public void rotate(int[] nums, int k) {
int N = nums.length;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < N; i++) {
map.put((i + k) % N, nums[i]);
}
for (int i = 0; i < N; i++) {
nums[i] = map.get(i);
}
}
// 思路2:三次翻转法
public void rotate2(int[] nums, int k) {
int n = nums.length;
k = (k % n);
// 整体翻转
reverse(nums, 0, n - 1);
// 0-k-1进行翻转
reverse(nums, 0, k - 1);
// k -n-1进行翻转
reverse(nums, k, n - 1);
}
public void reverse(int[] nums, int left, int right) {
while (left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
Code 198:打家劫舍
描述:计算在不能偷窃相邻房屋的情况下,能够偷窃到的最高金额。
Java
// 方法1:暴力递归
public int rob(int[] nums) {
return process(nums, nums.length, 0);
}
public int process(int[] nums, int n, int index) {
if (index >= n) {
return 0;
}
int p1 = process(nums, n, index + 1);
int p2 = nums[index] + process(nums, n, index + 2);
return Math.max(p1, p2);
}
// 方法2:动态规划
public int rob2(int[] nums) {
int N = nums.length;
int[] dp = new int[N + 1];
for (int i = N - 1; i >= 0; i--) {
int p1 = dp[i + 1];
int p2 = nums[i] + ((i + 2 > N) ? 0 : dp[i + 2]);
dp[i] = Math.max(p1, p2);
}
return dp[0];
}
// 方法3:空间压缩
public int rob3(int[] nums) {
int N = nums.length;
if (N == 0) return 0;
int cur = nums[N - 1];
int pre = 0;
for (int i = N - 2; i >= 0; i--) {
int temp = cur;
cur = Math.max(pre + nums[i], cur);
pre = temp;
}
return cur;
}
Code 200:岛屿数量
描述:计算网格中岛屿的数量('1'代表陆地,'0'代表水)。
Java
// 方法1:暴力递归 (感染法)
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0)
return 0;
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
res++;
affect(grid, i, j);
}
}
}
return res;
}
private void affect(char[][] grid, int x, int y) {
if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length)
return;
if (grid[x][y] == '1') {
grid[x][y] = 2; // 标记已访问
affect(grid, x - 1, y);
affect(grid, x + 1, y);
affect(grid, x, y - 1);
affect(grid, x, y + 1);
}
}
// 方法2:并查集
public int numIslands2(char[][] grid) {
UnionSet unionSet = new UnionSet(grid);
int row = grid.length;
int col = grid[0].length;
for (int i = 1; i < col; i++) {
if (grid[0][i] == '1' && grid[0][i - 1] == '1') {
unionSet.union(0, i, 0, i - 1);
}
}
for (int i = 1; i < row; i++) {
if (grid[i][0] == '1' && grid[i - 1][0] == '1') {
unionSet.union(i, 0, i - 1, 0);
}
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (grid[i][j] == '1') {
if (grid[i - 1][j] == '1') unionSet.union(i, j, i - 1, j);
if (grid[i][j - 1] == '1') unionSet.union(i, j, i, j - 1);
}
}
}
return unionSet.size;
}
class UnionSet {
int col, size, limit;
Map<Integer, Integer> fatherMap;
Map<Integer, Integer> sizeMap;
UnionSet(char[][] grid) {
this.col = grid[0].length;
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
size = 0;
limit = grid.length * grid[0].length;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
int index = i * col + j;
fatherMap.put(index, index);
sizeMap.put(index, 1);
size += 1;
}
}
}
}
public void union(int i1, int j1, int i2, int j2) {
int f1 = findFather(i1, j1), f2 = findFather(i2, j2);
if (f1 != f2) {
if (sizeMap.get(f1) >= sizeMap.get(f2)) {
sizeMap.put(f1, sizeMap.get(f1) + sizeMap.get(f2));
fatherMap.put(f2, f1);
} else {
sizeMap.put(f2, sizeMap.get(f1) + sizeMap.get(f2));
fatherMap.put(f1, f2);
}
size -= 1;
}
}
public int findFather(int i, int j) {
int index = i * col + j;
List<Integer> path = new ArrayList<>();
while (index != fatherMap.get(index)) {
path.add(index);
index = fatherMap.get(index);
}
for (int p : path) fatherMap.put(p, index);
return index;
}
}
Code 206:翻转链表
描述:反转一个单链表。
Java
public ListNode reverseList(ListNode head) {
ListNode pre = null;
while (head != null) {
ListNode next = head.next; // 保存下一个节点
head.next = pre; // 当前节点指向前一个节点
pre = head; // 前一个节点更新为当前节点
head = next; // 当前节点更新为下一个节点
}
return pre;
}
Code 207:课程表
描述:通过拓扑排序判断是否可以完成所有课程的学习。
Java
// 方法1:自定义图结构
public boolean canFinish1(int numCourses, int[][] prerequisites) {
Graph graph = new Graph();
Map<Integer, GraphNode> map = new HashMap<>();
for (int i = 0; i < prerequisites.length; i++) {
int from = prerequisites[i][1];
int to = prerequisites[i][0];
if (!map.containsKey(from)) {
map.put(from, new GraphNode(from));
graph.nodes.add(map.get(from));
}
if (!map.containsKey(to)) {
map.put(to, new GraphNode(to));
graph.nodes.add(map.get(to));
}
GraphNode fromNode = map.get(from);
GraphNode toNode = map.get(to);
fromNode.nexts.add(toNode);
toNode.in++;
}
Queue<GraphNode> queue = new LinkedList<>();
for (GraphNode node : map.values()) {
if (node.in == 0) queue.add(node);
}
int count = 0;
while (!queue.isEmpty()) {
GraphNode node = queue.poll();
count++;
for (Object next : node.nexts) {
GraphNode to = (GraphNode) next;
to.in--;
if (to.in == 0) queue.add(to);
}
}
return count == map.size();
}
// 方法2:邻接表法 (更简洁)
public boolean canFinish2(int numCourses, int[][] prerequisites) {
if (numCourses == 0 || prerequisites == null || prerequisites.length == 0) {
return true;
}
Map<Integer, List<Integer>> graph = new HashMap<>();
Map<Integer, Integer> inMap = new HashMap<>();
for (int[] p : prerequisites) {
inMap.put(p[0], inMap.getOrDefault(p[0], 0) + 1);
inMap.putIfAbsent(p[1], 0);
graph.computeIfAbsent(p[1], k -> new ArrayList<>()).add(p[0]);
}
Queue<Integer> queue = new LinkedList<>();
for (int key : inMap.keySet()) {
if (inMap.get(key) == 0) queue.add(key);
}
int nodes = 0;
while (!queue.isEmpty()) {
nodes++;
List<Integer> nexts = graph.get(queue.poll());
if (nexts != null) {
for (int n : nexts) {
inMap.put(n, inMap.get(n) - 1);
if (inMap.get(n) == 0) queue.add(n);
}
}
}
return nodes == inMap.size();
}
Code 215:数组中的第K个最大元素
描述:在未排序的数组中找到第 k个最大的元素。
Java
// 思路1:小根堆
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int i = 0; i < nums.length; i++) {
pq.add(nums[i]);
if (pq.size() > k) pq.poll();
}
return pq.peek();
}
// 思路2:改写快排 (Quick Select)
public int findKthLargest2(int[] nums, int k) {
return process(nums, 0, nums.length - 1, nums.length - k); // 找第k大即找第n-k小
}
public int process(int[] nums, int l, int r, int k) {
if (l >= r) return nums[l];
int pivot = nums[l + (int)(Math.random()*(r-l+1))];
int[] range = partition(nums, l, r, pivot);
if (k >= range[0] && k <= range[1]) return nums[k];
else if (k < range[0]) return process(nums, l, range[0] - 1, k);
else return process(nums, range[1] + 1, r, k);
}
public int[] partition(int[] nums, int l, int r, int val) {
int left = l - 1, right = r + 1, i = l;
while (i < right) {
if (nums[i] == val) i++;
else if (nums[i] < val) swap(nums, i++, ++left);
else swap(nums, i, --right);
}
return new int[]{left + 1, right - 1};
}
// 思路3:BFPRT (确定性划分)
public int findKthLargest3(int[] nums, int k) {
return bfprt(nums, 0, nums.length - 1, nums.length - k);
}
public int bfprt(int[] nums, int l, int r, int k) {
if (l == r) return nums[l];
int pivot = medianOfMedians(nums, l, r);
int[] range = partition(nums, l, r, pivot);
if (k >= range[0] && k <= range[1]) return nums[k];
else if (k < range[0]) return bfprt(nums, l, range[0] - 1, k);
else return bfprt(nums, range[1] + 1, r, k);
}
public int medianOfMedians(int[] nums, int l, int r) {
int size = r - l + 1;
int numMedians = size / 5 + (size % 5 == 0 ? 0 : 1);
int[] medians = new int[numMedians];
for (int i = 0; i < numMedians; i++) {
int subL = l + i * 5;
int subR = Math.min(r, subL + 4);
medians[i] = getMedian(nums, subL, subR);
}
return bfprt(medians, 0, medians.length - 1, medians.length / 2);
}
public int getMedian(int[] nums, int l, int r) {
insertionSort(nums, l, r);
return nums[l + (r - l) / 2];
}
public void insertionSort(int[] nums, int l, int r) {
for (int i = l + 1; i <= r; i++) {
int key = nums[i], j = i - 1;
while (j >= l && nums[j] > key) {
nums[j + 1] = nums[j--];
}
nums[j + 1] = key;
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}

9万+

被折叠的 条评论
为什么被折叠?



