力扣HOT100 - 1. 两数之和
解题思路:
解法一:暴力
class Solution {
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++) {
if (target == nums[i] + nums[j])
return new int[] { i, j };
}
return new int[0];
}
}
解法二:哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; ++i) {
if (map.containsKey(target - nums[i])) {
//保证返回的下标由小到大
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return new int[0];
}
}
力扣HOT100 - 49. 字母异位词分组
解题思路:
排序
注意:
返回时不能用List,因为List是抽象类,return的必须是List的具体实现,如ArrayList
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] array = str.toCharArray();
Arrays.sort(array);
String key = new String(array);
List<String> list = map.getOrDefault(key, new ArrayList<String>());
list.add(str);
map.put(key, list);
}
//返回时不能用List,而必须是List的具体实现,如ArrayList
return new ArrayList<List<String>>(map.values());
}
}
力扣HOT100 - 128. 最长连续序列
解题思路:
注意:
1.Set不能直接排序,必须要转换成ArrayList或者LinkedList后用Collections.sort()方法进行排序。
(Queue也不能直接排序,排序方法同Set)
2.连续的序列不能只找第一个,因为不一定是最长的,需要全部遍历完才能找到最长序列。
class Solution {
public int longestConsecutive(int[] nums) {
if (nums.length == 0) return 0;
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
set.add(nums[i]);
}
List<Integer> list = new ArrayList<>(set);
Collections.sort(list);
//cnt的长度从1开始,本身长度为1
int cnt = 1;
int max = 0;
//减一是为了避免越界
for (int i = 0; i < list.size() - 1; i++) {
if (list.get(i) == list.get(i + 1) - 1) {
cnt++;
} else {
max = Math.max(cnt, max);
//重置cnt的值
cnt = 1;
}
}
max = Math.max(cnt, max);
return max;
}
}
力扣HOT100 - 283. 移动零
解题思路:
双指针
指针 i 用于寻找不为零的位置
指针 j 用于寻找为零的位置
找到不为零时位置时与为零的位置进行交换,即nums[ i ]与nums[ j ]交换,使零向后移动,i 和 j 同时向下一个位置移动。
class Solution {
public void moveZeroes(int[] nums) {
if (nums == null) return;
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j++] = tmp;//j++所到的位置还是零
}
}
}
}
力扣HOT100 - 11. 盛最多水的容器
解题思路:
双指针,从左右两边往内侧夹逼,遍历后得到最大值
class Solution {
public int maxArea(int[] height) {
int i = 0, j = height.length - 1, res = 0;
while(i < j) {
res = height[i] < height[j] ?
Math.max(res, (j - i) * height[i++]):
Math.max(res, (j - i) * height[j--]);
}
return res;
}
}
力扣HOT100 - 15. 三数之和
解题思路:
排序 + 双指针
注意:
在nums[ k ],nums[ i ],nums[ j ]的值与自身重复时均会进行跳过,防止重复添加。
如代码中:
防止nums[ k ]重复:if(k>0&&nums[k]==nums[k-1]) continue;
防止nums[ i ]重复:while(i<j&&nums[i]==nums[++i]);
防止nums[ j ]重复:while(i<j&&nums[j]==nums[--j]);
class Solution {
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;
}
}
力扣HOT100 - 42. 接雨水
解题思路:
动态规划
感觉不是很好想
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) return 0;
int[] leftMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
int[] rightMax = new int[n];
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
int res = 0;
for (int i = 0; i < n; i++) {
res += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return res;
}
}
力扣HOT100 - 3. 无重复字符的最长子串
解题思路:
滑动窗口
注意:
left = Math.max(left,map.get(ch[i]) + 1);通过取最大值可以保证left不往回走
如果只是left = map.get(ch[i]) + 1; 在某些情况下也是正确的,但在一些特殊情况下可能会导致错误的结果。考虑以下情况:
假设字符串为 “abba”,当遍历到第二个 a 字符时,如果用 start = map.get('a') + 1;那么left将指向第一个 a 的下一个位置(设这个位置为 x ),而实际上left原先在循环到第二个 b 时就已经到了 x 的下一个位置。这样left往回走会导致错误的结果。
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.length() == 0) return 0;
Map<Character, Integer> map = new HashMap<>();
char[] ch = s.toCharArray();
int max = 0;
int left = 0;
for(int i = 0; i < ch.length; i++) {
if (map.containsKey(ch[i])) {
left = Math.max(left,map.get(ch[i]) + 1);
}
map.put(ch[i], i);
max = Math.max(max, i - left + 1);
}
return max;
}
}
力扣HOT100 - 438. 找到字符串中所有字母异位词
解题思路:
滑动窗口
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res = new ArrayList<>();
int sLen = s.length();
int pLen = p.length();
if (sLen < pLen) return res;
// 建立两个数组存放字符串中字母出现的词频,并以此作为标准比较
int[] scount = new int[26];
int[] pcount = new int[26];
// 当滑动窗口的首位在s[0]处时 (相当于放置滑动窗口进入数组)
for (int i = 0; i < pLen; i++) {
scount[s.charAt(i) - 'a']++; // 记录s中前pLen个字母的词频
pcount[p.charAt(i) - 'a']++; // 记录要寻找的字符串中每个字母的词频(只用进行一次来确定)
}
// 判断放置处是否有异位词 (在放置时只需判断一次)
if (Arrays.equals(scount, pcount)) res.add(0);
// 开始让窗口进行滑动
for (int i = 0; i < sLen - pLen; i++) { // i是滑动前的首位
scount[s.charAt(i) - 'a']--; // 将滑动前首位的词频删去
scount[s.charAt(i + pLen) - 'a']++; // 增加滑动后最后一位的词频(以此达到滑动的效果)
// 判断滑动后处,是否有异位词
if (Arrays.equals(scount, pcount)) res.add(i + 1);
}
return res;
}
}
力扣HOT100 - 560. 和为k的子数组
解题思路:
方法一:枚举
class Solution {
public int subarraySum(int[] nums, int k) {
int cnt = 0;
for (int start = 0; start < nums.length; start++) {
int sum = 0;
//注意开始位置
for (int end = start; end < nums.length; end++) {
sum += nums[end];
if (sum == k) {
cnt++;
}
}
}
return cnt;
}
}
方法二:前缀和 + 哈希表优化
通过计算前缀和,我们可以将问题转化为求解两个前缀和之差等于k的情况。
假设数组的前缀和数组为prefixSum,其中prefixSum[i]表示从数组起始位置到第i个位置的元素之和。那么对于任意的两个下标i和j(i < j),如果prefixSum[j] - prefixSum[i] = k,即从第i个位置到第j个位置的元素之和等于k,那么说明从第i+1个位置到第j个位置的连续子数组的和为k。
通过遍历数组,计算每个位置的前缀和,并使用一个哈希表来存储每个前缀和出现的次数。在遍历的过程中,我们检查是否存在prefixSum[j] - k的前缀和,如果存在,说明从某个位置到当前位置的连续子数组的和为k,我们将对应的次数累加到结果中。
public class Solution {
public static int subarraySum(int[] nums, int k) {
int count = 0;
int sum = 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1); // 初始化前缀和为0的次数为1
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (map.containsKey(sum - k)) {
count += map.get(sum - k);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return count;
}
}
力扣HOT100 - 239. 滑动窗口最大值
解题思路:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0 || k == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
// 未形成窗口
for(int i = 0; i < k; i++) {
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
deque.addLast(nums[i]);
}
res[0] = deque.peekFirst();
// 形成窗口后
for(int i = k; i < nums.length; i++) {
//因为窗口要滑动,所以如果双端队列的最大值就是数组的第一个元素,会被滑动出去
if(deque.peekFirst() == nums[i - k])
deque.removeFirst();
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
deque.addLast(nums[i]);
res[i - k + 1] = deque.peekFirst();
}
return res;
}
}
力扣HOT100 - 76. 最小覆盖子串
解题思路:
class Solution {
public String minWindow(String s, String t) {
if (s == null || s.isEmpty() || t == null || t.isEmpty() || s.length() < t.length())
return "";
Map<Character, Integer> tmap = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
// 记录 t 中每个字符的出现次数
for (char c : t.toCharArray())
tmap.put(c, tmap.getOrDefault(c, 0) + 1);
int left = 0, right = 0;// 窗口的左右边界
int valid = 0;// 已经匹配上的字符数量
int start = 0, minLen = Integer.MAX_VALUE;// 最小窗口的起始位置和长度
while (right < s.length()) {
char r = s.charAt(right);
right++;
if (tmap.containsKey(r)) {
window.put(r, window.getOrDefault(r, 0) + 1);
//不能用==,window.get(r) 和 tmap.get(r) 都返回的是 Integer 类型的对象。当使用 == 进行比较时,实际上比较的是两个 Integer 对象的引用,而非它们的值。
if (window.get(r).equals(tmap.get(r)))
valid++;
}
while (valid == tmap.size()) {
if (right - left < minLen) {
start = left;
minLen = right - left;
}
char l = s.charAt(left);
left++;
if (tmap.containsKey(l)) {
window.put(l, window.get(l) - 1);
if (window.get(l) < tmap.get(l))
valid--;
}
}
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}
}
力扣HOT100 - 53. 最大子数组和
解题思路:
class Solution {
public int maxSubArray(int[] nums) {
int res = Integer.MIN_VALUE;
int tempTotal = 0;
for(int i=0;i<nums.length;i++){
tempTotal+=nums[i];
res = Math.max(res,tempTotal);
// 如果和小于0,就重置为0
if(tempTotal<0) tempTotal = 0;
}
return res;
}
}
力扣HOT100 - 56. 合并区间
解题思路:
class Solution {
public int[][] merge(int[][] intervals) {
// 先按照区间起始位置排序
Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
int[][] res = new int[intervals.length][2];
int idx = -1;
for (int[] interval : intervals) {
//直接加入的情况,空二维数组第一次加入元素或二维数组中最后一个区间的右端小于新区间的左端,那么一定不重合
if (idx == -1 || res[idx][1] < interval[0])
res[++idx] = interval;
//合并区间的两种情况
//二维数组中最后一个区间的右端大于等于新区间的左端
//或最后一个区间整体包含新区间(考虑这两种情况所以才取最大值)
else{
res[idx][1] = Math.max(interval[1], res[idx][1]);
}
}
return Arrays.copyOf(res, idx + 1);
}
}
力扣HOT100 - 189. 轮转数组
解题思路:
三次反转。
先反转一次,再根据 k 拆分成两部分各反转一次。
class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
力扣HOT100 - 238. 除自身以外数组的乘积
解题思路:
当前位置的结果就是它左部分的乘积再乘以它右部分的乘积。因此需要进行两次遍历,第一次遍历用于求左部分的乘积,第二次遍历在求右部分的乘积的同时,再将最后的计算结果一起求出来。
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length;
if (len == 0)
return new int[0];
int[] ans = new int[len];
ans[0] = 1;
int tmp = 1;
//左半部分乘积
for (int i = 1; i < len; i++) {
ans[i] = ans[i - 1] * nums[i - 1];
}
//右半部分乘积,巧妙地使用tmp
for (int i = len - 2; i >= 0; i--) {
tmp *= nums[i + 1];
ans[i] *= tmp;
}
return ans;
}
}
力扣HOT100 - 41. 缺失的第一个正数
解题思路:
原地哈希
就相当于,让每个数字n都回到下标为n-1的家里。
而那些没有回到家里的就成了孤魂野鬼流浪在外,他们要么是根本就没有自己的家(数字小于等于0或者大于nums.size()),要么是自己的家被别人占领了(出现了重复)。
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for (int i = 0; i < len; i++) {
// 不能使用nums[i]-1!=i来判断,外面同时套了一层nums[]
while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i])
swap(nums, i, nums[i] - 1);
}
for (int i = 0; i < len; i++) {
if (nums[i] != i + 1)
return i + 1;
}
return len + 1;
}
public void swap(int[] nums, int idx1, int idx2) {
int tmp = nums[idx1];
nums[idx1] = nums[idx2];
nums[idx2] = tmp;
}
}
力扣HOT100 - 73. 矩阵置零
解题思路:
题目要求使用原地算法
在原地算法中,输入数据通常在内存中直接被修改,而不需要额外的辅助数据结构来存储中间结果。原地算法的一个常见应用是在数组或列表上进行操作,而不是创建新的数组或列表来存储结果。
class Solution {
public void setZeroes(int[][] matrix) {
int row = matrix.length; // 矩阵的行数
int col = matrix[0].length; // 矩阵的列数
boolean row0_flag = false; // 标记第一行是否有零
boolean col0_flag = false; // 标记第一列是否有零
// 检查第一行是否有零,遍历第一行需要知道有多少列
for (int j = 0; j < col; j++) {
if (matrix[0][j] == 0) {
row0_flag = true;
break;
}
}
// 检查第一列是否有零,遍历第一列需要知道有多少行
for (int i = 0; i < row; i++) {
if (matrix[i][0] == 0) {
col0_flag = true;
break;
}
}
// 使用第一行和第一列作为标志位
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = matrix[0][j] = 0; // 如果元素为零,则将对应的第一行和第一列的元素设置为零
}
}
}
// 根据第一行和第一列的标志位,将矩阵中的元素置零
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0) {
matrix[i][j] = 0; // 如果第一行或第一列的元素为零,则将当前元素置零
}
}
}
// 如果第一行有零,则将第一行所有元素置零,遍历第一行需要知道有多少列
if (row0_flag) {
for (int j = 0; j < col; j++) {
matrix[0][j] = 0;
}
}
// 如果第一列有零,则将第一列所有元素置零,遍历第一列需要知道有多少行
if (col0_flag) {
for (int i = 0; i < row; i++) {
matrix[i][0] = 0;
}
}
}
}
力扣HOT100 - 54. 螺旋矩阵
解题思路:
设置四个边界
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
if (matrix.length == 0) return new ArrayList<Integer>();
int l = 0;
int r = matrix[0].length - 1;
int t = 0;
int b = matrix.length - 1;
List<Integer> res = new ArrayList<>();
while (true) {
for (int i = l; i <= r; i++) res.add(matrix[t][i]);
if (++t > b) break;
for (int i = t; i <= b; i++) res.add(matrix[i][r]);
if (--r < l) break;
for (int i = r; i >= l; i--) res.add(matrix[b][i]);
if (--b < t) break;
for (int i = b; i >= t; i--) res.add(matrix[i][l]);
if (++l > r) break;
}
return res;
}
}
力扣HOT100 - 48. 旋转图像
解题思路:
要求原地旋转
可以先上下翻转,再沿主对角线反转(左上到右下的对角线)
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 上下翻转
for (int i = 0; i < n / 2; i++) {
for (int j = 0; j < n; j++) {
int tmp = matrix[i][j];
matrix[i][j] = matrix[n - i - 1][j];
matrix[n - i - 1][j] = tmp;
}
}
// 沿主对角线(\)翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
int tmp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = tmp;
}
}
}
}
力扣HOT100 - 240. 搜索二维矩阵 II
解题思路:
从左下角开始,根据条件删除行和列。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row = matrix.length - 1;
int col = matrix[0].length - 1;
int l = 0;
while (row >= 0 && l <= col) {
if (target < matrix[row][l]) {
row--;
} else if (target > matrix[row][l]) {
l++;
} else {
return true;
}
}
return false;
}
}
力扣HOT100 - 160. 相交链表
解题思路:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode pa = headA;
ListNode pb = headB;
while (pa != pb) {
pa = (pa != null) ? pa.next : headB;
pb = (pb != null) ? pb.next : headA;
if (pa == null && pb == null) return null;
}
return pa;
}
}
力扣HOT100 - 206. 反转链表
解题思路:
迭代(双指针)
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while(cur != null) {
ListNode tmp = cur.next; // 暂存后继节点 cur.next
cur.next = pre; // 修改 next 引用指向
pre = cur; // pre 暂存 cur
cur = tmp; // cur 访问下一节点
}
return pre;
}
}
力扣HOT100 - 234. 回文链表
解题思路:
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> list = new ArrayList<Integer>();
// 将链表的值复制到数组中
ListNode cur = head;
while (cur != null) {
list.add(cur.val);
cur = cur.next;
}
// 使用双指针判断是否回文
int l = 0;
int r = list.size() - 1;
while (l < r) {
if (!list.get(l).equals(list.get(r))) {
return false;
}
l++;
r--;
}
return true;
}
}
力扣HOT100 - 141. 环形链表
解题思路:
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null) {
if (!set.add(head)) {
return true;
}
head = head.next;
}
return false;
}
}
力扣HOT100 - 142. 环形链表 II
解题思路:
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null) {
if (!set.add(head)) {
return head;
}
head = head.next;
}
return null;
}
}
力扣HOT100 - 21. 合并两个有序链表
解题思路:
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dum = new ListNode(0), cur = dum;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2;
return dum.next;
}
}
力扣HOT100 - 2. 两数相加
解题思路:
缺位的节点进行补零处理,如973+23补充为973+023
注意相加的进位问题
class Solution {
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;
}
}
力扣HOT100 - 19. 删除链表的倒数第N个节点
解题思路:
链表题目:哑节点、栈、快慢指针(双指针)
方法一:计算链表长度
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dum = new ListNode(0, head);
int len = getLen(head);
ListNode cur = dum;
for (int i = 0; i < len - n; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
return dum.next;
}
public int getLen(ListNode head) {
int len = 0;
while (head != null) {
head = head.next;
len++;
}
return len;
}
}
方法二:栈
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dum = new ListNode(0, head);
Deque<ListNode> stack = new LinkedList<>();
ListNode cur=dum;
while(cur!=null){
stack.push(cur);
cur=cur.next;
}
for(int i=0;i<n;i++){
stack.pop();
}
ListNode pre=stack.peek();
pre.next=pre.next.next;
return dum.next;
}
}
方法三:快慢指针(双指针)
注意fast从head开始,而不是dum
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dum = new ListNode(0, head);
ListNode fast=head;
ListNode slow=dum;
for(int i=0;i<n;i++){
fast=fast.next;
}
while(fast!=null){
slow=slow.next;
fast=fast.next;
}
slow.next=slow.next.next;
return dum.next;
}
}
力扣HOT100 - 24. 两两交换链表中的节点
解题思路:
递归
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = head.next;
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
}
}
力扣HOT100 - 25. K 个一组翻转链表
解题思路:
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dum = new ListNode(0, head);
ListNode pre = dum;
ListNode end = dum;
while (end.next != null) {
for (int i = 0; i < k && end != null; i++) {
end = end.next;
}
if (end == null) break;
ListNode start = pre.next;
ListNode next = end.next;
end.next = null;
pre.next = reverse(start);
start.next = next;
pre = start;
end = start;
}
return dum.next;
}
public ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
力扣HOT100 - 138. 随机链表的复制
解题思路:
class Solution {
public Node copyRandomList(Node head) {
if(head==null) return null;
Node p = head;
//第一步,在每个原节点后面创建一个新节点
//1->1'->2->2'->3->3'
while(p!=null) {
Node newNode = new Node(p.val);
newNode.next = p.next;
p.next = newNode;
p = newNode.next;
}
p = head;
//第二步,设置新节点的随机节点
while(p!=null) {
if(p.random!=null) {
p.next.random = p.random.next;
}
p = p.next.next;
}
p = head;
Node anotherNode = new Node(-1);//定义一个另外的新节点,给它赋值为-1
Node cur = anotherNode;
//第三步,将两个链表分离
while(p!=null) {
cur.next = p.next;
cur = cur.next;
p.next = cur.next;
p = p.next;
}
return anotherNode.next;
}
}
注意:
Node p=head;与Node p=new Node(head.val);存在区别
前者的p为一个指向head对象的引用,换句话说,p和head 指向同一个对象,它们共享相同的引用,后者则创建一个全新的Node节点,p与head的val值相同。
力扣HOT100 - 148. 排序链表
解题思路:
归并排序
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode fast = head.next, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode temp = slow.next;
slow.next = null;
ListNode left = sortList(head);
ListNode right = sortList(temp);
ListNode dum = new ListNode(0);
ListNode cur = dum;
while (left != null && right != null) {
if (left.val < right.val) {
cur.next = left;
left = left.next;
} else {
cur.next = right;
right = right.next;
}
cur = cur.next;
}
cur.next = left != null ? left : right;
return dum.next;
}
}
力扣HOT100 - 23. 合并K个升序链表
解题思路:
只要会合并两个升序链表,合并K个做法类似。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode res = null;
for (int i = 0; i < lists.length; i++) {
res = merge(res, lists[i]);
}
return res;
}
public ListNode merge(ListNode l1, ListNode l2) {
if (l1 == null || l2 == null)
return l1 == null ? l2 : l1;
ListNode dum = new ListNode(0);
ListNode cur = dum;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dum.next;
}
}
力扣HOT100 - 146. LRU缓存
解题思路:
哈希表 + 双向链表
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
力扣HOT100 - 94. 二叉树的中序遍历
解题思路:
递归
class Solution {
List<Integer> list = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
recur(root);
return list;
}
public void recur(TreeNode root) {
if (root == null) return;
recur(root.left);
list.add(root.val);
recur(root.right);
}
}
力扣HOT100 - 104. 二叉树的最大深度
解题思路:
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
力扣HOT100 - 226. 翻转二叉树
解题思路:
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
力扣HOT100 - 101. 对称二叉树
解题思路:
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
if (L == null && R == null) return true;
if (L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}
力扣HOT100 - 543. 二叉树的直径
解题思路:
class Solution {
int ans;//记录节点数
public int diameterOfBinaryTree(TreeNode root) {
ans = 1;
depth(root);
return ans - 1;//节点数减 1 就是路径长度
}
public int depth(TreeNode root) {
if (root == null) return 0;
int l = depth(root.left);
int r = depth(root.right);
ans = Math.max(ans, l + r + 1);
return Math.max(l, r) + 1;
}
}
力扣HOT100 - 102. 二叉树的层序遍历
解题思路:
广度优先遍历
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
Deque<TreeNode> queue = new LinkedList<>();
if (root != null) queue.add(root);
while (!queue.isEmpty()) {
List<Integer> list = new ArrayList<>();
for (int i = queue.size(); i > 0; i--) {
TreeNode tmp = queue.pop();
if (tmp.left != null) queue.add(tmp.left);
if (tmp.right != null) queue.add(tmp.right);
list.add(tmp.val);
}
res.add(list);
}
return res;
}
}
力扣HOT100 - 108. 将有序数组转换为二叉搜索树
解题思路:
二叉搜索树一般使用中序遍历
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return helper(nums,0,nums.length-1);
}
public TreeNode helper(int[] nums,int left,int right){
if(left>right) return null;
//确定根节点
//总是选择中间位置左边的数字作为根节点
//也可以用 int mid = (left + right + 1) / 2; 总是选择中间位置右边的数字作为根节点
int mid=(left+right)/2;
TreeNode root=new TreeNode(nums[mid]);
root.left=helper(nums,left,mid-1);
root.right=helper(nums,mid+1,right);
return root;
}
}
力扣HOT100 - 98. 验证二叉搜索树
解题思路:
class Solution {
public boolean isValidBST(TreeNode root) {
return recur(root,Long.MIN_VALUE,Long.MAX_VALUE);
}
public boolean recur(TreeNode root,long lower,long upper){
if(root==null) return true;
if(root.val<=lower||root.val>=upper) return false;
return recur(root.left,lower,root.val)&&recur(root.right,root.val,upper);
}
}
力扣HOT100 - 230. 二叉搜索树中第K小的元素
解题思路:
class Solution {
List<Integer> list = new ArrayList<>();
public int kthSmallest(TreeNode root, int k) {
dfs(root);
return list.get(k - 1);
}
public void dfs(TreeNode root) {
if (root == null) return;
dfs(root.left);
list.add(root.val);
dfs(root.right);
}
}
力扣HOT100 - 199. 二叉树的右视图
解题思路:
相当于层序遍历,然后取每一层的最后一个节点。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
if (root == null) return new ArrayList<Integer>();
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> list = new ArrayList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = null;
for (int i = queue.size(); i > 0; i--) {
node = queue.poll();//经过for循环遍历覆盖后,保留了最右边的
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
list.add(node.val);//添加最右边的
}
return list;
}
}
力扣HOT100 - 114. 二叉树展开为链表
解题思路:
class Solution {
List<TreeNode> list = new ArrayList<>();
public void flatten(TreeNode root) {
recur(root);
for (int i = 1; i < list.size(); i++) {
TreeNode pre = list.get(i - 1);
TreeNode cur = list.get(i);
pre.left = null;
pre.right = cur;
}
}
public void recur(TreeNode root) {
if (root == null) return;
list.add(root);
recur(root.left);
recur(root.right);
}
}
力扣HOT100 - 105. 从前序与中序遍历序列构造二叉树
解题思路:
分治
以中序遍历为参照,用前序遍历的节点构建二叉树。
root + 1 + index - left表示前序遍历右子树的开始节点,即当前节点的下一个节点+左子树长度。
class Solution {
int[] preorder;
HashMap<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return recur(0, 0, inorder.length - 1);
}
TreeNode recur(int root, int left, int right) {
if (left > right) return null;
TreeNode node = new TreeNode(preorder[root]);
int index = map.get(preorder[root]);
node.left = recur(root + 1, left, index - 1);
node.right = recur(root + 1 + index - left, index + 1, right);
return node;
}
}
力扣HOT100 - 437. 路径总和 III
解题思路:
深度优先搜索
搜索所有节点的情况。
class Solution {
public int pathSum(TreeNode root, long targetSum) {
if (root == null) return 0;
int res = recur(root, targetSum);
res += pathSum(root.left, targetSum);
res += pathSum(root.right, targetSum);
return res;
}
public int recur(TreeNode root, long targetSum) {
int res = 0;//以新节点为起点,res置0
if (root == null) return 0;
int val = root.val;
if (val == targetSum) res++;
res += recur(root.left, targetSum - val);
res += recur(root.right, targetSum - val);
return res;
}
}
力扣HOT100 - 236. 二叉树的最近公共祖先
解题思路:
dfs
节点p,q异侧时,节点root为它们的公共祖先。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == root || q == root) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left == null && right == null) return null;
if (left == null) return right;
if (right == null) return left;
return root;
}
}
力扣HOT100 - 124. 二叉树中的最大路径和
解题思路:
class Solution {
int max = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxGain(root);
return max;
}
public int maxGain(TreeNode node) {
if (node == null) return 0;
int leftGain = Math.max(maxGain(node.left), 0);
int rightGain = Math.max(maxGain(node.right), 0);
int priceNewPath = node.val + leftGain + rightGain;
max = Math.max(priceNewPath, max);
return node.val + Math.max(leftGain, rightGain);
}
}
力扣HOT100 - 200. 岛屿数量
解题思路:
岛屿题目一般使用dfs。
1.判断是否越界
2.用0,1,2三个状态标识当前格子的状态(三个状态比两个状态更清晰)
3.向周围四个方向遍历
class Solution {
public int numIslands(char[][] grid) {
int cnt = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
cnt++;
}
}
}
return cnt;
}
public void dfs(char[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0' || grid[i][j] == '2') return;
grid[i][j] = '2';
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
}
力扣HOT100 - 994. 腐烂的橘子
解题思路:
因为要记录轮数(分钟数),所以不能一口气遍历到底,所以不能用深搜(bfs),而要用广搜(bfs,层序遍历)。
先记录下新鲜橘子数,并用一个队列记录腐烂橘子的坐标。
每轮遍历腐烂橘子(使用过的腐烂橘子需要出队),并向四周影响,使得四周的新鲜橘子变为腐烂橘子(新鲜橘子数减1,队列中加入新的腐烂橘子的坐标)。
class Solution {
public int orangesRotting(int[][] grid) {
Queue<int[]> queue = new LinkedList<>();
int fresh = 0;
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[0].length; c++) {
if (grid[r][c] == 1) fresh++;
else if (grid[r][c] == 2) queue.add(new int[] { r, c });
}
}
int minutes = 0;
while (fresh > 0 && !queue.isEmpty()) {
minutes++;
int n = queue.size();// for循环中queue大小不断变化,需要提前暂存
for (int i = 0; i < n; i++) {
int[] orange = queue.poll();
int r = orange[0];
int c = orange[1];
if (r - 1 >= 0 && grid[r - 1][c] == 1) {
grid[r - 1][c] = 2;
fresh--;
queue.add(new int[] { r - 1, c });
}
if (r + 1 < grid.length && grid[r + 1][c] == 1) {
grid[r + 1][c] = 2;
fresh--;
queue.add(new int[] { r + 1, c });
}
if (c - 1 >= 0 && grid[r][c - 1] == 1) {
grid[r][c - 1] = 2;
fresh--;
queue.add(new int[] { r, c - 1 });
}
if (c + 1 < grid[0].length && grid[r][c + 1] == 1) {
grid[r][c + 1] = 2;
fresh--;
queue.add(new int[] { r, c + 1 });
}
}
}
if (fresh > 0) return -1;
else return minutes;
}
}
力扣HOT100 - 207. 课程表
解题思路:
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] inDegree = new int[numCourses];//存每个结点的入度
List<List<Integer>> res = new ArrayList<>();//存结点之间依赖关系
Queue<Integer> queue = new LinkedList<>();
//初始化二维List集合
for(int i = 0; i < numCourses; i++)
res.add(new ArrayList<>());
//取出每一对结点
for(int[] temp : prerequisites){
inDegree[temp[0]]++;
res.get(temp[1]).add(temp[0]);
}
//先把所有入度为0的结点加入队列
for(int i = 0; i < numCourses; i++)
if(inDegree[i] == 0)
queue.add(i);
while(!queue.isEmpty()){
int pre = queue.poll();
numCourses--;
//根据依赖关系,把入度-1
for(int cur : res.get(pre)){
if(--inDegree[cur] == 0)
queue.add(cur);
}
}
return numCourses == 0;
}
}
力扣HOT100 208. 实现Trie(前缀树)
解题思路:
class Trie {
private Trie[] children; // 存储子节点的数组
private boolean isEnd; // 记录是否为单词结尾
public Trie() {
children = new Trie[26]; // 数组大小为26,代表26个小写字母
isEnd = false;
}
public void insert(String word) {
Trie node = this; // 从根节点开始
for (int i = 0; i < word.length(); i++) {
char ch = word.charAt(i);
int index = ch - 'a'; // 计算字母对应的索引
if (node.children[index] == null) {
node.children[index] = new Trie(); // 如果当前节点的子节点为空,创建一个新节点
}
node = node.children[index]; // 移动到子节点
}
node.isEnd = true; // 标记当前节点为单词结尾
}
public boolean search(String word) {
Trie node = searchPrefix(word); // 查找前缀
return node != null && node.isEnd; // 判断是否找到前缀并且是否为单词结尾
}
public boolean startsWith(String prefix) {
return searchPrefix(prefix) != null; // 判断是否找到前缀
}
private Trie searchPrefix(String prefix) {
Trie node = this; // 从根节点开始
for (int i = 0; i < prefix.length(); i++) {
char ch = prefix.charAt(i);
int index = ch - 'a'; // 计算字母对应的索引
if (node.children[index] == null) {
return null; // 如果当前节点的子节点为空,返回null
}
node = node.children[index]; // 移动到子节点
}
return node; // 返回最后一个节点
}
}
力扣HOT100 - 46. 全排列
解题思路:
回溯
假设给定数组nums为[1, 2, 3],首先将其转换为List<Integer>类型的output为[1, 2, 3]。
在backtrack方法中,初始时first为0,所以进入第一个for循环,交换output中第一个元素和自身,然后递归调用backtrack方法,此时first为1,再次进入for循环,交换output中第二个元素(即2)和自身。这样得到的output为[1, 2, 3],添加到结果集中。
接着回到第一个for循环,再次交换output中第一个元素和自身,此时成为[2, 1, 3],再次递归调用backtrack方法,得到的output为[2, 1, 3],添加到结果集中。
依次类推,通过不断交换元素的位置和递归调用backtrack方法,可以生成所有可能的排列组合。最终得到的结果为[[1, 2, 3], [2, 1, 3], [3, 1, 2], [1, 3, 2], [2, 3, 1], [3, 2, 1]],即给定数组[1, 2, 3]的全排列结果。
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> output = new ArrayList<>();
for (int num : nums) {
output.add(num);
}
int n = nums.length;
backtrack(n, output, res, 0);
return res;
}
public void backtrack(int n, List<Integer> output, List<List<Integer>> res, int first) {
if (first == n)
res.add(new ArrayList<Integer>(output));
for (int i = first; i < n; i++) {
Collections.swap(output, first, i);
backtrack(n, output, res, first + 1);
Collections.swap(output, first, i);
}
}
}
力扣HOT100 - 78. 子集
解题思路:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>(); // 解集
lists.add(new ArrayList<Integer>()); // 首先将空集加入解集中
for(int i = 0; i < nums.length; i++){
int size = lists.size(); // 当前子集数
for(int j = 0; j < size; j++){
List<Integer> newList = new ArrayList<>(lists.get(j));// 拷贝所有子集
newList.add(nums[i]); // 向拷贝的子集中加入当前数形成新的子集
lists.add(newList); // 向lists中加入新子集
}
}
return lists;
}
}
力扣HOT100 - 17. 电话号码的字母组合
解题思路:
class Solution {
public List<String> letterCombinations(String digits) {
List<String> combinations = new ArrayList<String>();
if (digits.length() == 0) {
return combinations;
}
Map<Character, String> phoneMap = new HashMap<Character, String>() {{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}};
backtrack(combinations, phoneMap, digits, 0, new StringBuffer());
return combinations;
}
public void backtrack(List<String> combinations, Map<Character, String> phoneMap, String digits, int index, StringBuffer combination) {
if (index == digits.length()) {
combinations.add(combination.toString());
} else {
char digit = digits.charAt(index);
String letters = phoneMap.get(digit);
int lettersCount = letters.length();
for (int i = 0; i < lettersCount; i++) {
combination.append(letters.charAt(i));
backtrack(combinations, phoneMap, digits, index + 1, combination);
combination.deleteCharAt(index);
}
}
}
}
力扣HOT100 - 39. 组合总和
解题思路:
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
int len = candidates.length;
List<List<Integer>> res = new ArrayList<>();
if (len == 0) return res;
Deque<Integer> path = new ArrayDeque<>();
dfs(candidates, 0, len, target, path, res);
return res;
}
private void dfs(int[] candidates, int begin, int len, int target, Deque<Integer> path, List<List<Integer>> res) {
if (target < 0) return;
if (target == 0) {
res.add(new ArrayList<>(path));
return;
}
//重点理解
for (int i = begin; i < len; i++) {
path.addLast(candidates[i]);
dfs(candidates, i, len, target - candidates[i], path, res);
path.removeLast();
}
}
}
力扣HOT100 - 22. 括号生成
解题思路:
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n <= 0) return res;
getParenthesis("", n, n);
return res;
}
private void getParenthesis(String str, int left, int right) {
if (left == 0 && right == 0) {
res.add(str);
return;
}
if (left == right) {
// 剩余左右括号数相等,下一个只能用左括号
getParenthesis(str + "(", left - 1, right);
} else if (left < right) {
// 剩余左括号小于右括号,下一个可以用左括号也可以用右括号
if (left > 0) getParenthesis(str + "(", left - 1, right);
if (right > 0) getParenthesis(str + ")", left, right - 1);
}
}
}
力扣HOT100 - 79. 单词搜索
解题思路:
深度优先搜索(DFS)+ 剪枝。
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if (dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
if (k == word.length - 1) return true;
board[i][j] = '\0';
boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
board[i][j] = word[k];
return res;
}
}
力扣HOT100 - 131. 分割回文串
解题思路:
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> path=new ArrayList<>();
public List<List<String>> partition(String s) {
backtrack(s,0);
return res;
}
public void backtrack(String s,int startIndex){
if(startIndex==s.length()){
res.add(new ArrayList<>(path));
return;
}
for(int i=startIndex+1;i<=s.length();i++){
String substring=s.substring(startIndex,i);
boolean flag=ishuiwen(substring);
//如果不是回文,那直接这条树枝全废了,也不用递归了,去同层的下一个节点了。
// 比如第一刀切出来个aab 已经不是回文了,那后面的bc 再怎么切也没用了。
// 所以直接continue,到切aabb去了
if(!flag) continue;
path.add(substring);
backtrack(s,i);
path.remove(path.size()-1);
}
}
boolean ishuiwen(String s){
int i=0;
int j=s.length()-1;
while(i<j){
if(s.charAt(i)!=s.charAt(j)) return false;
i++;
j--;
}
return true;
}
}
力扣HOT100 - 51. N 皇后
解题思路:
class Solution {
public List<List<String>> solveNQueens(int n) {
//定义一个返回结果的集合
List<List<String>> res = new ArrayList<>();
//定义一个存储皇后的集合
int[] queens = new int[n];
//填充数组queens[]中的每个元素都是-1
//queens={-1,-1,-1...-1}
Arrays.fill(queens, -1);
//定义一个变量,来记录当前元素所在的列,并将他所在的列标记为不可放元素
Set<Integer> columns = new HashSet<>();
//定义一个变量,来记录当前元素所在的左对角线,并将他所在的左对角线标记为不可放元素
Set<Integer> diagonals1= new HashSet<>();
//定义一个变量,来纪律当前元素所在的右对角线,并将他所在的右对角线标记为不可放元素
Set<Integer> diagonals2 = new HashSet<>();
//深度优先搜索方法
dfs(res, queens, n, 0, columns,diagonals1,diagonals2);
return res;
}
public void dfs(List<List<String>> res, int[] queens,int n,int row, Set<Integer> columns,Set<Integer> diagonals1,Set<Integer> diagonals2){
//如果当前遍历到最后一行,就说明存在一个解法
//所以将皇后的位置,存放入结果中
if(row == n){
//用来将当前的N行N列中的元素所在的位置结果,转换格式
List<String> board = generateBoard(queens, n);
//将符合条件的结果添加进返回结果集中
res.add(board);
}else{
//遍历所有行
for(int i = 0; i < n; i++){
//用来标记,当前行元素所在的列,都不可放元素
if(columns.contains(i)){
continue;
}
//去除左对角线上的所有元素
//row 表示行,i表示列
int diagonal1 = row-i;
if(diagonals1.contains(diagonal1)){
continue;
}
//去除右对角线上的元素
int diagonal2 = row + i;
if(diagonals2.contains(diagonal2)){
continue;
}
//经过上面的三次排除,就可以找到元素在当前行的哪一列的位置。
//选第一行的第几列,也可以叫单元格所在的位置
queens[row] = i;
//把选中的单元格加入到,去除列的集合中
//用来给下一行的元素所在的列作为排除条件判断
columns.add(i);
//把选中的单元格加入到,去除左对角线的集合中
diagonals1.add(diagonal1);
//把选中的单元格加入到,去除右对角线的集合中
diagonals2.add(diagonal2);
//递归遍历下一行,
dfs(res,queens,n,row+1,columns,diagonals1,diagonals2);
//剪枝操作
queens[row] = -1;
//将当前列和左对角线和右对角线的元素都删除,避免重复遍历
columns.remove(i);
diagonals1.remove(diagonal1);
diagonals2.remove(diagonal2);
}
}
}
//转换格式
public List<String> generateBoard(int[] queens,int n){
//定义一个结果集,用于返回结果
List<String> board = new ArrayList<>();
//遍历所有行
for(int i = 0; i < n; i++){
char[] row = new char[n];
Arrays.fill(row, '.');
//将当前行所在的列的,位置置为Q
row[queens[i]] = 'Q';
//将当前结果添加进结果集中
board.add(new String(row));
}
return board;
}
}
力扣HOT100 - 35. 搜索插入位置
解题思路:
二分法模板
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
}
力扣HOT100 - 74. 搜索二维矩阵
解题思路:
两次二分,第一次定位行,第二次定位列。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int l = 0, r = m - 1;
//定位行
int row = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (matrix[mid][0] <= target && matrix[mid][n - 1] >= target) {
row = mid;
break;
} else if (target < matrix[mid][0]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
if (row == -1) return false;
//定位列
l = 0;
r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
//已知行row之后,就按照常规的二分来做
if (target == matrix[row][mid])
return true;
else if (target < matrix[row][mid])
r = mid - 1;
else
l = mid + 1;
}
return false;
}
}
力扣HOT100 - 34. 在排序数组中查找元素的第一个和最后一个位置
解题思路:
两次二分,第一次定左边的位置,第二次定右边的位置,也算模板。
class Solution {
public int[] searchRange(int[] nums, int target) {
int l = 0, r = nums.length - 1;
int first = -1, last = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[mid] == target) {
first = mid;
r = mid - 1;//重点,向左找
} else if (nums[mid] > target) {
r = mid - 1;
} else {
l = mid + 1;
}
}
l = 0;
r = nums.length - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[mid] == target) {
last = mid;
l = mid + 1;//重点,向右找
} else if (nums[mid] > target) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return new int[] { first, last };
}
}
力扣HOT100 - 33. 搜索旋转排序数组
解题思路:
旋转排序数组可分为N1 + N2两个部分,如:[4,5,6,7,1,2,3],N1为[4,5,6,7],N2为[1,2,3]
必然满足以下两个条件:
1. N1和N2都是分别递增的;
2. N1中的所有元素大于N2中的所有元素;
以上两个条件可推出:nums[0]是N1中最小的数,即nums[0] > N2中的所有元素
而mid不是在N1内就是在N2内
所以:如果nums[0] <= nums[mid],即mid落在了N1内,则[0, mid]肯定是有序的
否则mid落在了N2内,则[mid, n)肯定是有序的
if (nums[0] <= nums[mid]) {
// 左半边有序
} else {
// 右半边有序
}
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n == 0) return -1;
if (n == 1) return nums[0] == target ? 0 : -1;
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[0] > nums[mid]) {
if (nums[mid] < target && target <= nums[n - 1]) {
l = mid + 1;
} else
r = mid - 1;
} else {
if (nums[0] <= target && target < nums[mid]) {
r = mid - 1;
} else
l = mid + 1;
}
}
return -1;
}
}
力扣HOT100 - 153. 寻找旋转排序数组中的最小值
解题思路:
与33题类似。
class Solution {
public int findMin(int[] nums) {
int l = 0;
int r = nums.length - 1;
if (nums[r] >= nums[l]) return nums[0];
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[0] > nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return nums[l];
}
}
力扣HOT100 - 4. 寻找两个正序数组的中位数
解题思路:
两个数组合并,然后根据奇偶返回中位数。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int[] nums = new int[m + n];
if (m == 0) {
if (n % 2 == 0) return (nums2[n / 2 - 1] + nums2[n / 2]) / 2.0;
else return nums2[n / 2];
}
if (n == 0) {
if (m % 2 == 0) return (nums1[m / 2 - 1] + nums1[m / 2]) / 2.0;
else return nums1[m / 2];
}
int cnt = 0;
int i = 0, j = 0;
while (cnt != (m + n)) {
if (nums1[i] < nums2[j]) nums[cnt++] = nums1[i++];
else nums[cnt++] = nums2[j++];
if (i == m) {
while (j != n)
nums[cnt++] = nums2[j++];
}
if (j == n) {
while (i != m)
nums[cnt++] = nums1[i++];
}
}
if (cnt % 2 == 0) return (nums[cnt / 2 - 1] + nums[cnt / 2]) / 2.0;
else return nums[cnt / 2];
}
}
力扣HOT100 - 20. 有效的括号
解题思路:
class Solution {
private static final Map<Character,Character> map = new HashMap<Character,Character>(){{
put('{','}'); put('[',']'); put('(',')'); put('?','?');
}};
public boolean isValid(String s) {
if(s.length() > 0 && !map.containsKey(s.charAt(0))) return false;
LinkedList<Character> stack = new LinkedList<Character>() {{ add('?'); }};
for(Character c : s.toCharArray()){
if(map.containsKey(c)) stack.addLast(c);
else if(map.get(stack.removeLast()) != c) return false;
}
return stack.size() == 1;
}
}
力扣HOT100 - 155. 最小栈
解题思路:
辅助栈
class MinStack {
private Stack<Integer> stack;
private Stack<Integer> min_stack;
public MinStack() {
stack = new Stack<>();
min_stack = new Stack<>();
}
public void push(int val) {
stack.push(val);
if (min_stack.isEmpty() || val <= min_stack.peek())
min_stack.push(val);
}
public void pop() {
if (stack.pop().equals(min_stack.peek()))
min_stack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return min_stack.peek();
}
}
力扣HOT100 - 394. 字符串解码
解题思路:
辅助栈
class Solution {
public String decodeString(String s) {
int k = 0;
StringBuilder res = new StringBuilder();
Stack<Integer> kstack = new Stack<>();
Stack<StringBuilder> restack = new Stack<>();
for(char c : s.toCharArray()){
if(c == '['){
//碰到括号,记录K和当前res,归零。
kstack.push(k);
restack.push(res);
k = 0;
res = new StringBuilder();
}else if(c ==']'){
//出最近的一个左括号入的k,当前res进行计算不入栈
int curk = kstack.pop();
StringBuilder temp = new StringBuilder();
for(int i = 0; i < curk; i++){
temp.append(res);
}
//与括号外合并
res = restack.pop().append(temp);
}else if(c >= '0' && c <= '9'){
k = c - '0' + k * 10;
//如果k是多位数需要x10
}else{
res.append(c);
//如果是字母则缓慢添加
}
}
return res.toString();
}
}
力扣HOT100 - 739. 每日温度
解题思路:
单调栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int length = temperatures.length;
int[] ans = new int[length];
Deque<Integer> stack = new LinkedList<>();
for (int i = 0; i < length; i++) {
int temperature = temperatures[i];
while (!stack.isEmpty() && temperature > temperatures[stack.peek()]) {
int preIndex = stack.pop();
ans[preIndex] = i - preIndex;
}
stack.push(i);
}
return ans;
}
}
力扣HOT100 - 84. 柱状图中最大的矩形
解题思路:
单调栈
对于一个高度height[ i ],找左右两边均严格小于它的值。
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
int[] right = new int[n];
Deque<Integer> mono_stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
mono_stack.pop();
}
left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
mono_stack.push(i);
}
mono_stack.clear();
for (int i = n - 1; i >= 0; i--) {
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
mono_stack.pop();
}
right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
mono_stack.push(i);
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
}
力扣HOT100 - 215. 数组中第K个最大元素
解题思路:
快速选择,目标是找出数组中第 k 小(或第 k 大)的元素,而不是对整个数组进行排序。
(需要和快排进行区分,快排的目的是排序)
注意:
i = l - 1, j = r + 1; 为什么要这么写?而不是 i = l; j = r ?
因为是先执行do语句的内容,一开始进循环就已经先i++或者j--了,所以进循环前需要-1和+1。
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
return quickselect(nums, 0, n - 1, n - k);
}
public int quickselect(int[] nums, int l, int r, int k) {
if (l == r) return nums[k];
int i = l - 1, j = r + 1;
while (i < j) {
do i++; while (nums[i] < nums[l]);
do j--; while (nums[j] > nums[l]);
if (i < j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
if (k <= j) return quickselect(nums, l, j, k);
else return quickselect(nums, j + 1, r, k);
}
}
力扣HOT100 - 347. 前K个高频元素
解题思路:
小根堆(顺便练习一下优先队列)
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
// int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
int num = entry.getKey(), cnt = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < cnt) {
queue.poll();
queue.offer(new int[] { num, cnt });
}
} else {
queue.offer(new int[] { num, cnt });
}
}
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = queue.poll()[0];
}
return res;
}
}
力扣HOT100 - 295. 数据流的中位数
解题思路:
小顶堆 + 大顶堆
class MedianFinder {
Queue<Integer> A, B;
public MedianFinder() {
A = new PriorityQueue<>();
B = new PriorityQueue<>((x, y) -> (y - x));
}
public void addNum(int num) {
if (A.size() != B.size()) {
A.add(num);
B.add(A.poll());
} else {
B.add(num);
A.add(B.poll());
}
}
public double findMedian() {
return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
}
}
力扣HOT100 - 121. 买卖股票的最佳时机
解题思路:
每次遍历price,更新最小的cost和最大的profit
class Solution {
public int maxProfit(int[] prices) {
int cost = Integer.MAX_VALUE, profit = 0;
for (int price : prices) {
cost = Math.min(cost, price);
profit = Math.max(profit, price - cost);
}
return profit;
}
}
力扣HOT100 - 55. 跳跃游戏
解题思路:
class Solution {
public boolean canJump(int[] nums) {
int n = nums.length;
int maxReach = 0;
// 正常来说每次至少跳一格,所以最多循环n次
for (int i = 0; i < n; i++) {
if (i > maxReach) return false;// 这种情况代表遇到了0,跳不动了
if (maxReach >= n - 1) return true;
maxReach = Math.max(maxReach, i + nums[i]);
}
return false;
}
}
力扣HOT100 - 45. 跳跃游戏 II
解题思路:
贪心
class Solution {
public int jump(int[] nums) {
int end = 0;
int maxPosition = 0;
int steps = 0;
for (int i = 0; i < nums.length - 1; i++) {
maxPosition = Math.max(maxPosition, i + nums[i]);
if (i == end) {
end = maxPosition;
steps++;
}
}
return steps;
}
}
力扣HOT100 - 763. 划分字母区间
解题思路:
class Solution {
public List<Integer> partitionLabels(String s) {
int[] last = new int[26];
int len = s.length();
for (int i = 0; i < len; i++) {
last[s.charAt(i) - 'a'] = i;//记录字母最远的下标
}
List<Integer> partition = new ArrayList<>();
int start = 0, end = 0;
for (int i = 0; i < len; i++) {
end = Math.max(end, last[s.charAt(i) - 'a']);
if (i == end) {
partition.add(end - start + 1);
start = end + 1;
}
}
return partition;
}
}
力扣HOT100 - 70. 爬楼梯
解题思路:
动态规划
注意 if 判断和 for 循环
class Solution {
public int climbStairs(int n) {
if (n <= 2) return n;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
力扣HOT100 - 118. 杨辉三角
解题思路:
每个数字等于上一行的左右两个数字之和。
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
List<Integer> row = new ArrayList<>();
for (int j = 0; j <= i; j++) {
if (j == 0 || j == i) row.add(1);
else row.add(res.get(i - 1).get(j - 1) + res.get(i - 1).get(j));
}
res.add(row);
}
return res;
}
}
力扣HOT100 - 198. 打家劫舍
解题思路:
动态规划
class Solution {
public int rob(int[] nums) {
int len = nums.length;
if (nums == null || len == 0) return 0;
if (len == 1) return nums[0];
int[] dp = new int[len];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < len; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[len - 1];
}
}
为什么不是int[] dp=new int[n+1];
下标从0开始就用new int[n]就行,如果从1开始会越界才会加1。
力扣HOT100 - 279. 完全平方数
解题思路:
动态规划
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
// 初始化dp数组,默认最坏情况是每个数都是由1相加得到的
for (int i = 1; i <= n; i++) {
dp[i] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= i; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
}
力扣HOT100 - 322. 零钱兑换
解题思路:
动态规划
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
力扣HOT100 - 139. 单词拆分
解题思路:
动态规划
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
力扣HOT100 - 300. 最长递增子序列
解题思路:
动态规划
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums.length == 0) return 0;
int[] dp = new int[nums.length];
int max = 0;
Arrays.fill(dp, 1);
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
力扣HOT100 - 152. 乘积最大子数组
解题思路:
方法一:暴力
class Solution {
public int maxProduct(int[] nums) {
int max = Integer.MIN_VALUE;
int s = 1;
for (int i = 0; i < nums.length; i++) {
s = 1;
for (int j = i ; j < nums.length; j++) {
s *= nums[j];
max = Math.max(max, s);
}
}
return max;
}
}
方法二:动态规划
class Solution {
public int maxProduct(int[] nums) {
int max = Integer.MIN_VALUE, imax = 1, imin = 1;
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0) {
int tmp = imax;
imax = imin;
imin = tmp;
}
imax = Math.max(imax * nums[i], nums[i]);
imin = Math.min(imin * nums[i], nums[i]);
max = Math.max(max, imax);
}
return max;
}
}
力扣HOT100 - 416. 分割等和子集
解题思路:
动态规划
对于当前考虑的元素 nums[i],如果 dp[ j - nums[ i ] ] 为 true,说明可以用前面的元素构成和为 j -nums[ i ] 的子集,那么在加上当前元素 nums[ i ] 的情况下,就可以构成和为 j 的子集,因此 dp[ j ] 更新为 true。如果 dp[ j - nums[ i ] ] 为 false,则说明无法使用前面的元素构成和为 j-nums[ i ] 的子集,那么无论如何也无法构成和为 j 的子集,因此 dp[ j ] 不变。
class Solution {
public boolean canPartition(int[] nums) {
int n = nums.length;
if (n < 2) return false;
int sum = 0, maxNum = 0;
for (int num : nums) {
sum += num;
maxNum = Math.max(maxNum, num);
}
if (sum % 2 != 0) return false;
int target = sum / 2;
if (maxNum > target) return false;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int i = 0; i < n; i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[target];
}
}
力扣HOT100 - 32. 最长有效括号
解题思路:
栈
class Solution {
public int longestValidParentheses(String s) {
int max = 0;
// 也可以使用 Stack<Integer> stack=new Stack<>();但Stack是遗留类,不推荐
Deque<Integer> stack = new LinkedList<>();
stack.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
stack.pop();
if (stack.isEmpty()) stack.push(i);
else max = Math.max(max, i - stack.peek());
}
}
return max;
}
}
力扣HOT100 - 62. 不同路径
解题思路:
动态规划
注意要初始化第一行和第一列的值
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int j = 0; j < n; j++) {
dp[0][j] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
}
力扣HOT100 - 64. 最小路径和
解题思路:
动态规划
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
if (grid == null || m == 0 || n == 0) return 0;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < n; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = Math.min(dp[i - 1][j] + grid[i][j], dp[i][j - 1] + grid[i][j]);
}
}
return dp[m - 1][n - 1];
}
}
力扣HOT100 - 5. 最长回文子串
解题思路:
动态规划
class Solution {
public String longestPalindrome(String s) {
// 特殊用例判断
int len = s.length();
if (len < 2) return s;
int maxLen = 1;
int begin = 0;
// dp[i][j] 表示 s[i, j] 是否是回文串
boolean[][] dp = new boolean[len][len];
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
for (int j = 1; j < len; j++) {
for (int i = 0; i < j; i++) {
if (s.charAt(i) != s.charAt(j)) dp[i][j] = false;
else {
if (j - i <= 2) dp[i][j] = true;
else dp[i][j] = dp[i + 1][j - 1];//向内收,看里面的串是不是回文串
}
// 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
力扣HOT100 - 1143. 最长公共子序列
解题思路:
动态规划
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
char c1 = text1.charAt(i - 1);
for (int j = 1; j <= n; j++) {
char c2 = text2.charAt(j - 1);
if (c1 == c2) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
}
力扣HOT100 - 72. 编辑距离
解题思路:
动态规划
class Solution {
public int minDistance(String word1, String word2) {
int n1 = word1.length();
int n2 = word2.length();
int[][] dp = new int[n1 + 1][n2 + 1];
for (int j = 1; j <= n2; j++) dp[0][j] = dp[0][j - 1] + 1;
for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
}
}
return dp[n1][n2];
}
}
力扣HOT100 - 136. 只出现一次的数字
解题思路:
class Solution {
public int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
}
力扣HOT100 - 169. 多数元素
解题思路:
有点类似于Boyer-Moore 投票算法,但更加形象。
class Solution {
public int majorityElement(int[] nums) {
int winner = nums[0];
int cnt = 1;
for (int i = 1; i < nums.length; i++) {
if (winner == nums[i]){
cnt++;
}
else if (cnt == 0) {
winner = nums[i];
cnt++;
} else {
cnt--;
}
}
return winner;
}
}
力扣HOT100 - 75. 颜色分类
解题思路:
单指针,对数组进行两次遍历。
class Solution {
public void sortColors(int[] nums) {
int p = 0;
int n = nums.length;
for (int i = 0; i < n; i++) {
if (nums[i] == 0) {
int tmp = nums[i];
nums[i] = nums[p];
nums[p] = tmp;
p++;
}
}
for (int i = p; i < n; i++) {
if (nums[i] == 1) {
int tmp = nums[i];
nums[i] = nums[p];
nums[p] = tmp;
p++;
}
}
}
}
力扣HOT100 - 31. 下一个排列
解题思路:
数字是逐步增大的
步骤如下:
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) i--;
if (i >= 0) {
int j = nums.length - 1;
while (j >= 0 && nums[i] >= nums[j]) j--;
swap(nums, i, j);
}
reverse(nums, i + 1);
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
public void reverse(int[] nums, int start) {
int left = start, right = nums.length - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
}
力扣HOT100 - 287. 寻找重复数
解题思路:
快慢指针
-
第一步,慢指针每次移动一步,快指针每次移动两步,直到它们相遇。这一步保证了它们在环中相遇。
-
接下来,将其中一个指针(快指针或慢指针)重置到起点(即数组的第一个位置),然后两个指针都每次只移动一步。它们再次相遇的位置就是环的起始节点,也就是数组中重复的元素。
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}