双指针
leetcode392. 判断子序列
法一:动态规划
法二:双指针
leetcode876. 链表的中间结点
法一:链表数组
法二:快慢指针
leetcode160. 相交链表
法一:双指针
leetcode167. 两数之和 II - 输入有序数组
法一:二分查找
法二:双指针
leetcode142. 环形链表 II
法一:快慢指针
leetcode151. 反转字符串中的单词
法一:双指针
法二:分割+倒序
leetcode3. 无重复字符的最长子串
法一:滑动窗口
法二:动态规划
leetcode15. 三数之和
法一:排序+双指针
leetcode239. 滑动窗口最大值
法一:单调队列
leetcode392. 判断子序列
392. 判断子序列https://leetcode.cn/problems/is-subsequence/
法一:动态规划
public class Method01 {// 检查字符串 s 是否为字符串 t 的子序列public boolean isSubsequence(String s, String t) {// 获取字符串 s 和 t 的长度int n = s.length();int m = t.length();// 创建一个布尔数组 dp,长度为 n+1,表示是否匹配到当前字符boolean[] dp = new boolean[n + 1];// 初始化 dp[0] 为 true,表示空字符串 s 一定是任意字符串 t 的子序列dp[0] = true;// 遍历字符串 tfor (int i = 1; i <= m; i++) {// 遍历字符串 sfor (int j = 1; j <= n; j++) {// 如果 dp[j] 为 true,说明这个字符已经匹配成功,继续下一个字符if (dp[j]) {continue;}// 如果 s 的当前字符与 t 的当前字符匹配if (s.charAt(j - 1) == t.charAt(i - 1)) {// 记录当前状态,如果匹配,说明可以通过 dp[j-1] 的状态来更新 dp[j]dp[j] = dp[j - 1];break; // 匹配成功后退出内层循环,继续检查 t 的下一个字符}}}// 返回 s 是否完整匹配到 treturn dp[n];}
}
法二:双指针
public class Method02 {// 检查字符串 s 是否为字符串 t 的子序列public boolean isSubsequence(String s, String t) {// 初始化两个指针,i 指向 s,j 指向 tint i = 0, j = 0;// 当 i 小于 s 的长度且 j 小于 t 的长度时继续循环while (i < s.length() && j < t.length()) {// 如果 s 和 t 当前字符匹配if (s.charAt(i) == t.charAt(j)) {i++; // 移动 s 的指针,匹配下一个字符}j++; // 始终移动 t 的指针,继续检查下一个字符}// 如果 i 等于 s 的长度,则说明所有字符均被匹配,返回 truereturn i == s.length();}
}
leetcode876. 链表的中间结点
876. 链表的中间结点https://leetcode.cn/problems/middle-of-the-linked-list/
法一:链表数组
public class Method01 {// 找到链表的中间节点public ListNode middleNode(ListNode head) {// 创建一个 ListNode 数组,用于存储链表的节点ListNode[] arr = new ListNode[100]; // 假设链表的最大长度不会超过 100int i = 0;// 遍历链表,将每个节点存入数组while (head != null) {arr[i++] = head; // 将当前节点赋值给数组head = head.next; // 移动到下一个节点}// 返回中间节点,如果链表节点数为偶数,则返回第二个中间节点return arr[i / 2];}
}
法二:快慢指针
public class Method02 {// 找到链表的中间节点public ListNode middleNode(ListNode head) {ListNode slow = head; // 初始化慢指针ListNode fast = head; // 初始化快指针// 使用快慢指针找到中间节点while (fast != null && fast.next != null) {slow = slow.next; // 慢指针每次移动一步fast = fast.next.next; // 快指针每次移动两步}// 返回慢指针指向的中间节点return slow;}
}
leetcode160. 相交链表
160. 相交链表https://leetcode.cn/problems/intersection-of-two-linked-lists/
法一:双指针
public class Method01 {// 找到两个链表的交点public ListNode getIntersectionNode(ListNode headA, ListNode headB) {ListNode pA = headA; // 指针 pA 初始化为链表 A 的头节点ListNode pB = headB; // 指针 pB 初始化为链表 B 的头节点// 当两个指针还未相遇时,继续遍历while (pA != pB) {// 如果 pA 到达链表 A 的末尾,则将其指向链表 B 的头节点pA = pA == null ? headB : pA.next;// 如果 pB 到达链表 B 的末尾,则将其指向链表 A 的头节点pB = pB == null ? headA : pB.next;}// 返回相遇的节点(交点),如果没有交点,则返回 nullreturn pA;}
}
leetcode167. 两数之和 II - 输入有序数组
167. 两数之和 II - 输入有序数组https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/
法一:二分查找
public class Method01 {// 找到数组中两个数的索引,使得这两个数的和等于目标值public int[] twoSum(int[] numbers, int target) {// 创建一个数组用于存储结果,长度为2int[] result = new int[2];// 遍历数组,外层循环固定一个元素for (int i = 0; i < numbers.length - 1; i++) {// 设置左右指针,左指针指向当前元素的下一个位置,右指针指向数组末尾int left = i + 1;int right = numbers.length - 1;// 使用二分查找找到与固定元素相加等于目标值的另一个元素while (left <= right) {// 计算中间位置int mid = (left + right) / 2;// 检查当前元素和中间元素的和是否等于目标值if (numbers[i] + numbers[mid] == target) {result[0] = i + 1; // 记录第一个元素的索引(加1以符合题目要求)result[1] = mid + 1; // 记录第二个元素的索引(加1以符合题目要求)return result; // 返回结果}// 如果和小于目标值,移动左指针向右else if (numbers[i] + numbers[mid] < target) {left = mid + 1;}// 如果和大于目标值,移动右指针向左else {right = mid - 1;}}}// 如果没有找到符合条件的两个元素,返回结果return result;}
}
法二:双指针
public class Method02 {// 找到数组中两个数的索引,使得这两个数的和等于目标值public int[] twoSum(int[] nums, int target) {int left = 0; // 初始化左指针,指向数组的开始int right = nums.length - 1; // 初始化右指针,指向数组的末尾// 当左指针小于右指针时,继续查找while (left < right) {int sum = nums[left] + nums[right]; // 计算当前左指针和右指针指向元素的和// 如果和等于目标值,返回两个指针索引(加1以符合题目要求)if (sum == target) {return new int[]{left + 1, right + 1};}// 如果和小于目标值,移动左指针向右else if (sum < target) {left++;}// 如果和大于目标值,移动右指针向左else {right--;}}// 如果没有找到符合条件的两个元素,返回一组表示未找到的索引return new int[]{-1, -1};}
}
leetcode142. 环形链表 II
142. 环形链表 IIhttps://leetcode.cn/problems/linked-list-cycle-ii/
法一:快慢指针
public class Method01 {// 找到链表中的环的起始节点,如果没有环则返回 nullpublic ListNode detectCycle(ListNode head) {ListNode slow = head; // 初始化慢指针,指向链表的头节点ListNode fast = head; // 初始化快指针,指向链表的头节点// 使用快慢指针找环while (fast != null && fast.next != null) {slow = slow.next; // 慢指针每次移动一步fast = fast.next.next; // 快指针每次移动两步// 如果慢指针和快指针相遇,说明链表有环if (slow == fast) {// 从链表头开始,另一个指针也从相遇点开始slow = head; // 将慢指针重新指向头节点// 匹配两个指针,相遇的点就是环的起始点while (slow != fast) {slow = slow.next; // 慢指针向前移动一步fast = fast.next; // 快指针向前移动一步}return slow; // 返回环的起始节点}}// 如果没有环,返回 nullreturn null;}
}
leetcode151. 反转字符串中的单词
151. 反转字符串中的单词https://leetcode.cn/problems/reverse-words-in-a-string/
法一:双指针
public class Method01 {// 反转给定字符串中的单词顺序public String reverseWords(String s) {// 去除字符串首尾的空白字符s = s.trim();StringBuilder sb = new StringBuilder(); // 创建一个 StringBuilder 用于构建结果字符串int i = s.length() - 1; // 设置指针 i 指向字符串的最后一个字符int j = i; // 设置指针 j,初始化为 i// 遍历字符串,从后往前反转单词while (i >= 0) {// 找到当前单词的开始位置while (i >= 0 && s.charAt(i) != ' ') {i--;}// 提取当前单词并追加到 StringBuilder 中,后面加一个空格sb.append(s.substring(i + 1, j + 1) + " ");// 跳过空格,移动指针到下一个单词的开头while (i >= 0 && s.charAt(i) == ' ') {i--;}j = i; // 更新指针 j 为当前指针 i}// 返回构建的字符串,去掉最后的多余空格return sb.toString().trim();}
}
法二:分割+倒序
public class Method02 {// 反转给定字符串中的单词顺序public String reverseWords(String s) {// 去除字符串首尾的空白字符s = s.trim();// 用空格分割字符串,得到单词数组String[] str = s.split(" ");// 创建一个 StringBuilder 用于构建结果字符串StringBuilder sb = new StringBuilder();// 从后往前遍历单词数组for (int i = str.length - 1; i >= 0; i--) {// 跳过空字符串(可能由于多空格导致拆分产生空元素)if (str[i].equals("")) continue;// 追加单词到 StringBuilder,后面加一个空格sb.append(str[i]).append(" ");}// 返回构建的字符串,去掉最后的多余空格return sb.toString().trim();}
}
leetcode3. 无重复字符的最长子串
3. 无重复字符的最长子串https://leetcode.cn/problems/longest-substring-without-repeating-characters/
法一:滑动窗口
public class Method01 {// 找到给定字符串中不重复字符的最长子串的长度public int lengthOfLongestSubstring(String s) {int i = 0; // 右指针,遍历字符串int j = -1; // 左指针,表示当前无重复字符子串的起始位置int max = 0; // 记录最大无重复字符子串的长度// 创建一个 HashMap,用于存储字符及其最新出现的位置Map<Character, Integer> map = new HashMap<>();// 遍历字符串,直到右指针 i 到达字符串末尾while (i < s.length()) {// 如果字符 s[i] 已经在 map 中,更新 j 为它上次出现的位置if (map.containsKey(s.charAt(i))) {j = Math.max(j, map.get(s.charAt(i)));}// 更新当前无重复字符子串的最大长度max = Math.max(max, i - j);// 存储当前字符及其最新出现的位置map.put(s.charAt(i), i);i++; // 移动右指针}// 返回找到的最大长度return max;}
}
法二:动态规划
public class Method02 {public int lengthOfLongestSubstring(String s) {Map<Character, Integer> map = new HashMap<>();int temp = 0, res = 0;for (int i = 0; i < s.length(); i++) {int j;if (map.containsKey(s.charAt(i))) {j = map.get(s.charAt(i));}else {j=-1;}map.put(s.charAt(i), i);temp = temp<i-j?temp+1:i-j;if (temp > res) {res = temp;}}return res;}
}
leetcode15. 三数之和
15. 三数之和https://leetcode.cn/problems/3sum/
法一:排序+双指针
public class Method01 {// 找到所有和为0的三元组public List<List<Integer>> threeSum(int[] nums) {// 对数组进行排序Arrays.sort(nums);int n = nums.length; // 数组长度List<List<Integer>> res = new ArrayList<>(); // 存储满足条件的三元组// 遍历数组,外层循环选定第一个数字for (int i = 0; i < n - 2; i++) {// 如果当前数字大于0,直接结束循环,因为后面的数字也都大于0,无法构成和为0的三元组if (nums[i] > 0) {break;}// 跳过重复元素以避免重复的三元组if (i > 0 && nums[i] == nums[i - 1]) {continue;}// 内层使用双指针法,j 指向 i 后面的元素,k 指向数组的最后一个元素int j = i + 1, k = n - 1;while (j < k) {// 计算三个数的和int sum = nums[i] + nums[j] + nums[k];// 如果和为0,找到一个三元组if (sum == 0) {res.add(Arrays.asList(nums[i], nums[j], nums[k])); // 添加三元组到结果列表j++; // 移动左指针k--; // 移动右指针// 跳过重复的元素,避免添加重复的三元组while (j < k && nums[j] == nums[j - 1]) {j++;}while (j < k && nums[k] == nums[k + 1]) {k--;}} else if (sum < 0) {// 如果和小于0,说明需要更大的数,移动左指针j++;} else {// 如果和大于0,说明需要更小的数,移动右指针k--;}}}// 返回所有找到的和为0的三元组return res;}
}
leetcode239. 滑动窗口最大值
239. 滑动窗口最大值https://leetcode.cn/problems/sliding-window-maximum/
法一:单调队列
public class Method01 {public int[] maxSlidingWindow(int[] nums, int k) {// 如果输入数组为空或窗口大小为0,则返回空数组if (nums.length == 0 || k == 0) return new int[0];// 使用双端队列来存储当前窗口的元素Deque<Integer> deque = new LinkedList<>();// 结果数组,长度为输入数组长度 - 窗口大小 + 1int[] res = new int[nums.length - k + 1];// 未形成完整窗口的情况,填充前 k 个元素for (int i = 0; i < k; i++) {// 移除队列中比当前元素小的所有元素while (!deque.isEmpty() && deque.peekLast() < nums[i]) {deque.removeLast(); // 去掉队尾元素}// 将当前元素添加到队列的尾部deque.addLast(nums[i]);}// 将队列的首部元素(当前窗口的最大值)存入结果数组res[0] = deque.peekFirst();// 从第 k 个元素开始,开始滑动窗口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; // 返回记录最大值的数组}
}