【代码随想录】双指针法刷题

移除元素

题目:27. 移除元素 - 力扣(LeetCode)

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

快慢指针:

public int removeElement(int[] nums, int val) {
    
    
	int l = 0;
	for (int r = 0; r < nums.length; r++)
		if (nums[r] != val) nums[l++] = nums[r];
	return l;
}

删除有序数组中的重复项

题目:26. 删除有序数组中的重复项 - 力扣(LeetCode)

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

快慢指针:

public int removeDuplicates(int[] nums) {
    
    
	int l = 1;
	for (int r = 1; r < nums.length; r++) 
		if (nums[r] != nums[l - 1]) nums[l++] = nums[r];
	return l;
}

移动零

题目:283. 移动零 - 力扣(LeetCode)

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

快慢指针:

class Solution {
    
    
    public void moveZeroes(int[] nums) {
    
    
        for (int l = 0, r = 0; r < nums.length; r++)
            if (nums[r] != 0) swap(nums, l++, r);
    }
    void swap(int[] nums, int i, int j) {
    
    
        nums[i] ^= nums[j];
        nums[j] ^= nums[i];
        nums[i] ^= nums[j];
    }
}

不使用交换的双指针:

public void moveZeroes(int[] nums) {
    
    
	int l = 0;
	// 将非 0 元素按顺序放到前面
	for (int r = 0; r < nums.length; r++)
		if (nums[r] != 0) nums[l++] = nums[r];
	// 后面全部赋 0
	while (l < nums.length) nums[l++] = 0;
}

比较含退格的字符串*

题目:844. 比较含退格的字符串 - 力扣(LeetCode)

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。

输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"。

从前往后重构字符串:

class Solution {
    
    
    public boolean backspaceCompare(String s, String t) {
    
    
        return getString(s).equals(getString(t));
    }

    public String getString(String s) {
    
    
        StringBuilder sb = new StringBuilder();
        char[] cs = s.toCharArray();
        for (int i = 0; i < cs.length; i++) {
    
    
            if (cs[i] != '#') sb.append(cs[i]);
            else if (sb.length() > 0) sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }
}

从后往前重构字符串:

class Solution {
    
    
    public boolean backspaceCompare(String s, String t) {
    
    
        return getString(s).equals(getString(t));
    }

    private String getString(String s) {
    
    
        StringBuilder sb = new StringBuilder();
        char[] cs = s.toCharArray();
        int size = 0; // '#' 数量
        for (int i = cs.length - 1; i >= 0; i--) {
    
    
            if (cs[i] == '#') size++; 
            else if (size == 0) sb.append(cs[i]); 
            else size--;
        }
        return sb.toString();
    }
}

O(1) 空间复杂度做法:比较难。。。

有序数组的平方

题目:977. 有序数组的平方 - 力扣(LeetCode)

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
public int[] sortedSquares(int[] nums) {
    
    
	int[] res = new int[nums.length];
	int l = 0, r = nums.length - 1;
	for (int i = nums.length - 1; i >= 0; i--) {
    
    
		if (nums[l] + nums[r] < 0) {
    
     // 左边绝对值大
			res[i] = nums[l] * nums[l++];
		} else {
    
     // 右边绝对值大
			res[i] = nums[r] * nums[r--];
		}
	}
	return res;
}

反转字符串

题目:344. 反转字符串 - 力扣(LeetCode)

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

使用位运算进行交换操作:

public void reverseString(char[] s) {
    
    
	int size = s.length - 1;
	for (int i = 0; i < s.length / 2; i++) {
    
    
		int j = size - i;
		s[i] ^= s[j];
		s[j] ^= s[i];
		s[i] ^= s[j];
	}
}

一般可以将 反转字符数组 抽取成模板(经常会用到这个函数)

class Solution {
    
    
    public void reverseString(char[] s) {
    
    
        reverse(s, 0, s.length - 1);
    }
	// 反转字符数组
    void reverse(char[] cs, int l, int r) {
    
    
        while (l < r) {
    
    
            cs[l] ^= cs[r];
            cs[r] ^= cs[l];
            cs[l++] ^= cs[r--];
        }
    }
}

替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

题目:剑指 Offer 05. 替换空格 - 力扣(LeetCode)

public String replaceSpace(String s) {
    
    
	StringBuilder sb = new StringBuilder();
	for (char c : s.toCharArray()) {
    
    
		if (c != ' ') sb.append(c);
		else sb.append("%20");
	}
	return sb.toString();
}

反转链表:递归 + 迭代 + 头插法

题目:206. 反转链表 - 力扣(LeetCode)

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

双指针:

public ListNode reverseList(ListNode head) {
    
    
	ListNode cur = head, pre = null;
	while (cur != null) {
    
    
		ListNode tmp = cur.next;
		cur.next = pre;
		pre = cur;
		cur = tmp;
	}
	return pre;
}

递归:

public ListNode reverseList(ListNode head) {
    
    
	if (head == null || head.next == null) return head;
	ListNode node = reverseList(head.next);
	head.next.next = head;
	head.next = null;
	return node;
}

头插法:空间 O(n)

public ListNode reverseList(ListNode head) {
    
    
	ListNode res = null;
	for (ListNode x = head; x != null; x = x.next)
		res = new ListNode(x.val, res);
	return res;
}

删除链表的倒数第 N 个节点*

题目:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

快慢指针:

  • 快指针先走 n 步,然后和慢指针同时开始走
  • 当快指针指向最后一个节点,此时慢指针指向的便是要删除的节点(的前一个节点)
public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
	if (head == null || head.next == null) return null;
	ListNode fast = head, slow = head;
	while (n-- > 0) fast = fast.next; // 快指针先走 n 步
	if (fast == null) return head.next; // 删除第一个节点
	while (fast.next != null) {
    
    
		fast = fast.next;
		slow = slow.next;
	}
	slow.next = slow.next.next;
	return head;
}

快慢指针 + 虚拟头节点:

一般使用虚拟头节点可以不用处理特殊情况

public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
	ListNode vn = new ListNode(0, head); // 虚拟头节点
	ListNode slow = vn, fast = vn;
	while (n-- > 0) fast = fast.next; // 快指针先走 n 步
	while (fast.next != null) {
    
    
		fast = fast.next;
		slow = slow.next;
	}
	slow.next = slow.next.next;
	return vn.next;
}

递归:

class Solution {
    
    
    int cur = 0;
    public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
        if (head == null) return null;
        head.next = removeNthFromEnd(head.next, n);
        cur++;
        if (cur == n) return head.next;
        return head;
    }
}

环形链表:快慢指针

题目:141. 环形链表 - 力扣(LeetCode)

给你一个链表的头节点 head ,判断链表中是否有环。

快慢指针:

public boolean hasCycle(ListNode head) {
    
    
	ListNode slow = head, fast = head;
	while (fast != null && fast.next != null) {
    
    
		slow = slow.next;
		fast = fast.next.next;
		if (slow == fast) return true;
	}
	return false;
}

哈希:

public boolean hasCycle(ListNode head) {
    
    
	Set<ListNode> set = new HashSet<>();
	while (head != null && !set.contains(head)) {
    
    
		set.add(head);
		head = head.next;
	}
	return head != null;
}

环形链表 II**

题目:142. 环形链表 II - 力扣(LeetCode)

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

快慢指针 判断环 + 找环入口:

public ListNode detectCycle(ListNode head) {
    
    
	ListNode slow = head, fast = head;
	// 快慢指针判断是否有环
	while (fast != null && fast.next != null) {
    
    
		slow = slow.next;
		fast = fast.next.next;
		if (slow == fast) {
    
     // 有环, 找环的起始点
			fast = head; // 快指针从头开始
			while (fast != slow) {
    
    
				fast = fast.next;
				slow = slow.next;
			}
			return fast;
		}
	}
	return null;
}

哈希:

public ListNode detectCycle(ListNode head) {
    
    
	Set<ListNode> set = new HashSet<>();
	while (head != null && !set.contains(head)) {
    
    
		set.add(head);
		head = head.next;
	}
	return head;
}

链表相交*

题目:面试题 02.07. 链表相交 - 力扣(LeetCode)

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

双指针:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
	ListNode curA = headA, curB = headB;
	int lenA = 0, lenB = 0;
	// 求链表 A 的长度
	while (curA != null) {
    
     
		lenA++; 
		curA = curA.next; 
	}
	// 求链表 B 的长度
	while (curB != null) {
    
     
		lenB++; 
		curB = curB.next; 
	}
	curA = headA;
	curB = headB;
	// 链表 A 更长则让 A 多走, B 更长则让 B 多走,保证 A B 从同一位置开始比较
	if (lenA > lenB) for (int i = 0; i < lenA - lenB; i++) curA = curA.next;
	else for (int i = 0; i < lenB - lenA; i++) curB = curB.next;
	// 逐个比较 A 和 B 
	while (curA != null) {
    
    
		if (curA == curB) return curA;
		curA = curA.next;
		curB = curB.next;
	}
	return curA;
}

巧妙做法:

  • a 走完走 b,b 走完走 a,如果有相交点,必然会遇到
  • 如果没有相交点,最终会在相遇在 null
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
	ListNode h1 = headA, h2 = headB;
	while (h1 != h2) {
    
    
		h1 = (h1 == null) ? headB : h1.next;
		h2 = (h2 == null) ? headA : h2.next;
	}
	return h1;
}

哈希:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
	Set<ListNode> set = new HashSet<>();
	ListNode p = headA;
	while (p != null) {
    
    
		set.add(p);
		p = p.next;
	}
	p = headB;
	while (p != null) {
    
    
		if (set.contains(p)) return p;
		p = p.next;
	}
	return null;
}

三数之和**

题目:15. 三数之和 - 力扣(LeetCode)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

思路:

  1. 排序数组
  2. 定义 i, j, k 三个指针。遍历 i,将问题转化成在 i 之后的数组中寻找 nums[j] = nums[k] = -nusm[i]
  3. 注意寻找同时要去重
public List<List<Integer>> threeSum(int[] nums) {
    
    
	List<List<Integer>> res = new ArrayList<>();
	int n = nums.length;
	Arrays.sort(nums);
	// 剪枝: 最小的数 > 0 或 最大的数 < 0, 不可能和为 0
	if (nums[0] > 0 || nums[n - 1] < 0) return res;
	// 转化成两数之和: 寻找 nums[j] + nums[k] = -nums[i]
	for (int i = 0; i < n; i++) {
    
    
		 if (nums[i] > 0) break; // 不可能找到和为 0 的三元组
		 if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重
		 // 两数之和寻找方法: 双指针, j 从前开始, k 从后开始
		 int j = i + 1, k = n - 1, target = -nums[i];
		 while (j < k) {
    
    
			if (nums[j] + nums[k] < target) j++;
			else if (nums[j] + nums[k] > target) k--;
			else {
    
    
				res.add(Arrays.asList(nums[i], nums[j], nums[k]));
				while (j < k && nums[j] == nums[j + 1]) j++; // 去重
				while (j < k && nums[k] == nums[k - 1]) k--; // 去重
				j++;
				k--;
			}
		 }
	}
	return res;
}

四数之和

题目:18. 四数之和 - 力扣(LeetCode)

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

public List<List<Integer>> fourSum(int[] nums, int target) {
    
    
	List<List<Integer>> res = new ArrayList<>();
	int n = nums.length;
	if (n < 4) return res;
	Arrays.sort(nums);
	// 处理越界的情况: 正 + 正 = 负 
	if (nums[0] > 0 && target <= 0 || nums[n - 1] < 0 && target >= 0) return res;

	for (int i = 0; i < n - 3; i++) {
    
    
		if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重
		// 剪枝: 当前情况的最小和 > target 或 最大和 < target
		if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
		if ((long) nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue;
		for (int j = i + 1; j < n - 2; j++) {
    
    
			if (j > i + 1 && nums[j] == nums[j - 1]) continue; // 去重
			// 剪枝: 当前情况的最小和 > target 或 最大和 < target
			if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) break;
			if ((long) nums[i] + nums[j] + nums[n - 2] + nums[n - 1] < target) continue;
			// 两数之和
			int k = j + 1, l = n - 1;
			while (k < l) {
    
    
				int sum = nums[i] + nums[j] + nums[k] + nums[l];
				if (sum < target) k++;
				else if (sum > target) l--;
				else {
    
    
					res.add(Arrays.asList(nums[i], nums[j], nums[k], nums[l]));
					while (k < l && nums[k] == nums[k + 1]) k++; // 去重
					while (k < l && nums[l] == nums[l - 1]) l--; // 去重
					k++;
					l--;
				}
			}
		}
	}
	return res;
}

分割线 -----

无重复字符的最长子串*

题目:3. 无重复字符的最长子串 - 力扣(LeetCode)

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

双指针:

public int lengthOfLongestSubstring(String s) {
    
    
	char[] cs = s.toCharArray();
	int[] map = new int[128]; // 字符 与 出现次数
	int res = 0;
	for (int l = 0, r = 0; r < cs.length; r++) {
    
    
		map[cs[r]]++; // 访问字符
		while (map[cs[r]] > 1) map[cs[l++]]--;
		res = Math.max(res, r - l + 1);
	}
	return res;
}

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/126733734