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

2023-11-17

移除元素

题目: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;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【代码随想录】双指针法刷题 的相关文章

随机推荐

  • IDEA中设置注释模板的方法

    IDEA中设置注释模板主要分为两个部分 分别是创建java文件时类的注释和方法的注释 这里为大家详细介绍一下方法 按MyEclipse的风格设置 MyEclipse的请看 MyEclipse中设置注释模板的方法 大家可以根据自己的习惯生成自
  • [Monana] Windows/Linux/mac下Anaconda3 Python3配置Tensorflow最简明教程~(只用一步)

    Authored by MonanaHe Contact me via hemonan vip 163 com 0 写在前面的话 为什么我敢说这是最简明的教程 网上很多人用conda安装tf 而且是单独装一个tensorflow的环境 这样
  • 2023华为OD机试真题(java 100%通过)【最多获得的短信条数/动态规划】

    题目内容 某云短信厂商 为庆祝国庆 推出充值优惠活动 现在给出客户预算 和优惠售价序列 求最多可获得的短信总条数 输入描述 第一行客户预算M 其中 0 M 10 6 第二行给出售价表 P1 P2 Pn 其中 1 n 100 Pi为充值 i
  • 最全Android 开发和安全系列工具

    阿里聚安全出品 史上最全Android 开发和安全系列工具 作者 菜刀文 关注 2017 02 20 00 08 字数 4554 阅读 725 评论 1 喜欢 29 作者 阿里聚安全 地址 https zhuanlan zhihu com
  • flex布局最后一行列表左对齐的方法

    使用flex布局两端对齐 但是最后一行元素居中会很丑 所以可以让最后一行元素左对齐 方法如下 改之前 html div class list box div class item div gt div css list box displa
  • SIP 抓包后获取媒体内容备忘(解析RTP)

    SIP呼叫并抓包 从网上找免费的sip 软中端 两个转中端建立呼叫且抓包 详情可以参考 https blog csdn net liuxingrui4p article details 96709136 spm 1001 2014 3001
  • C++ 编译报错“jump to label”

    C 编译报错 jump to label 分析 解决方法 如何在Eclipse中添加编译选项 分析 void func int a 0 a goto label label int b 0 return 这样的代码是有问题的 因为C 编译规
  • Python计算机视觉编程(八)图像检索

    图像检索 BOW模型 基于BOW的图像检索 特征提取 视觉词典 TF IDF 常用参数 图像检索 具体实现流程 BOW模型 Bag of words models模型 词袋模型 词袋模型对于给定的两个文档 进行分割可以建构出一个有n个元素词
  • L2-040 哲哲打游戏 (25 分)(分析题目意思,读懂题)

    哲哲是一位硬核游戏玩家 最近一款名叫 达诺达诺 的新游戏刚刚上市 哲哲自然要快速攻略游戏 守护硬核游戏玩家的一切 为简化模型 我们不妨假设游戏有 N 个剧情点 通过游戏里不同的操作或选择可以从某个剧情点去往另外一个剧情点 此外 游戏还设置了
  • Ubuntu常用环境配置

    配置软件源 切换清华源 sudo sed i s http archive ubuntu com https mirrors tuna tsinghua edu cn g etc apt sources list sudo sed i s
  • react教程-井字棋案例扩展03

    使用两个循环来渲染出棋盘的格子 而不是在代码里写死 hardcode 这个关键点是在于循环 react的循环是和vue不一样的 veu中的循环是直接写在节点上 但是react的循环 是通过数组遍历的方法 先遍历出虚拟的Dom节点 然后通过r
  • Active Directory的基本认识

    参考文献 http edu yesky com edupxpt 379 2086379 shtml http en wikipedia org wiki Active Directory Active Directory 和我们熟悉的网络邻
  • 通讯编程002——使用Arduino ModbusTCP 控制照明

    本文介绍如何使用Arduino作为Modbus TCP从站 PC机为Modbus主站 安装ModScan用于主站调试 通过ModScan控制LED开关照明 相关软件可登录网信智汇 wangxinzhihui 下载 1 运行Arduino I
  • 一份关于jvm内存调优及原理的学习笔记

    JVM 一 虚拟机的基本结构 1 jvm整体架构 类加载子系统 负责从文件系统或者网络中加载class信息 存入方法区中 方法区 Perm 存放加载后的class信息 包括静态方法 jdk1 6以前包含了常量池 参数 XX PermSize
  • Android获取SHA1

    SHA1 怎么获取 不同签名文件的 SHA1 值不同 可以参考下面三种获取 SHA1 值的方式 1 通过 Android Studio 编译器获取 1 打开 Android Studio 的 Terminal 工具 2 输入命令 keyto
  • html从一个页面跳转至另一个html页面的子页面

    假设从1 html跳转至2 html的子页面 则 在1 html中添加点击事件 a href user customerManageNew class u btn add span class swf add span a 然后在后台con
  • Android中 @id 与 @+id 区别

    Android 中的组件需要用一个int 类型的值来表示 这个值也就是组件标签中的id 属性值 id 属性只能接受资源类型的值 也就是必须以 开头的值 例如 id abc id xyz等 如果在 后面使用 表示当修改完某个布局文件并保存后
  • shell脚本中的几个括号总结(小括号/大括号/花括号)

    转自 http www cnblogs com hanyan225 archive 2011 10 06 2199652 html Shell的强大是毋庸置疑的 方便了我们也迷惑了我们 比如这些杂七杂八的括号 一向自认聪明的我也傻傻分不清了
  • uni-app request回调函数内无法使用this.

    微信小程序开发中 通常会在 request成功的回调函数中修改本地的属性 如果直接使用this 会有类似的提示无法修改 gt Cannot set property xxx of undefined at api request succe
  • 【代码随想录】双指针法刷题

    双指针法刷题 移除元素 删除有序数组中的重复项 移动零 比较含退格的字符串 有序数组的平方 反转字符串 替换空格 反转链表 递归 迭代 头插法 删除链表的倒数第 N 个节点 环形链表 快慢指针 环形链表 II 链表相交 三数之和 四数之和