算法练习高频「双指针」类型题目总结
Posted 在路上的德尔菲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法练习高频「双指针」类型题目总结相关的知识,希望对你有一定的参考价值。
题目一 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
元素可以为负数,如果有负数,我们知道负负得正,两边的元素会比中间元素平方数大,所以我们使用双指针法可以很好去判断左右哪个数更大。
注意:while(left <= right)
,如果写成while(left < right)
将会遗漏一个元素的计算,举个栗子,假如有两个元素[1,2],结果将是[1,4]
题解:
class Solution {
public int[] sortedSquares(int[] nums) {
int len = nums.length - 1, left = 0, right = nums.length - 1;
int[] res = new int[nums.length];
while(left <= right){
if(nums[left] * nums[left] > nums[right] * nums[right]){
res[len--] = nums[left] * nums[left];
left++;
}else{
res[len--] = nums[right] * nums[right];
right--;
}
}
return res;
}
}
时间复杂度:O(N)
空间复杂度:O(N)
链接:https://leetcode-cn.com/problems/squares-of-a-sorted-array
题目二 旋转数组
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?🤔
示例:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
题解一:
申请新的数组空间,将原数组中各个元素按照右移数放进新数组中
class Solution {
public void rotate(int[] nums, int k) {
int len = nums.length;
int[] res = new int[len];
for(int i = 0; i < len; i++){
res[(i + k) % len] = nums[i];
}
for(int i = 0; i < len; i++){
nums[i] = res[i];
}
}
}
时间复杂度:O(N)
空间复杂度:O(N)
题解二:
方法比较巧,用上面的输入举个🌰
输入:[1,2,3,4,5,6,7] k =3
第一步:[7,6,5,4,3,2,1] 所有元素均翻转
第二步:[5,6,7,4,3,2,1] 翻转下标范围为[0, k-1]元素
第三步:[5,6,7,1,2,3,4] 翻转下标范围为[k, len-1]元素
class Solution {
public void rotate(int[] nums, int k) {
int len = nums.length;
k = k % len;
reverse(nums, 0, len - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, len - 1);
}
public void reverse(int[] nums, int left, int right) {
while (left < right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
right--;
left++;
}
}
}
时间复杂度:O(N),每个元素被翻转两次,O(2N) = O(N)
空间复杂度:O(1) ,原地翻转
链接:https://leetcode-cn.com/problems/rotate-array
题目三 反转字符串中的单词 III
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
示例:
输入:"Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"
这道理虽然是道easy,但是我觉得并不easy 😂
- 找到一个完整单词
- 一个完整单词翻转,先加单词最右侧的字符,直到单词最左侧的字符
- 单词之间空格处理,添加空字符
- 如果还没到数组weid
注意:
- 不要越界
right < len
- 空格不一定只有一个,可能有多个,所以需要对空格特别处理
题解:
class Solution {
public String reverseWords(String s) {
int len = s.length();
StringBuilder sb = new StringBuilder();
int right = 0;
while (right < len) {
int left = right;
//找到一个完整单词
while (right < len && s.charAt(right) != ' ') {
right++;
}
//一个完整单词翻转,先加单词最右侧的字符,直到单词最左侧的字符
for (int i = left; i < right; i++) {
sb.append(s.charAt(right - 1 - (i - left)));
}
//单词之间空格处理
while (right < len && s.charAt(right) == ' ') {
right++;
sb.append(' ');
}
}
return sb.toString();
}
}
时间复杂度:O(N)
空间复杂度:O(N)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-words-in-a-string-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题目四 两数之和 II - 输入有序数组
给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
题解:
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0,right = numbers.length - 1;
while(left < right){
if(numbers[left] + numbers[right] == target){
break;
}
while(left < right && numbers[left] + numbers[right] > target){
right--;
}
while(left < right && numbers[left] + numbers[right] < target){
left++;
}
}
return new int[]{left + 1, right + 1};
}
}
时间复杂度:O(N),其中 N是数组的长度,两个指针移动的总次数最多为N次。
空间复杂度:O(1)
链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted
题目五 链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
题解:
快慢指针,举个🌰奇数链表[1,2,3,4,5]
慢指针:1->2->3
快指针:1->3->5
偶数链表情况[1,2,3,4,5,6]
慢指针:1->2->3->4
快指针:1->3->5->null
注意:fast != null
和fast.next != null
避免空指针
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow = head,fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
时间复杂度:O(N)
空间复杂度:O(1)
题目六 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:尝试使用一趟扫描实现
示例:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
输入:head = [1,2], n = 1
输出:[1]
输入:head = [1,2], n = 2
输出:[2]
输入:head = [1], n = 1
输出:[]
题解一:
先遍历一趟得到链表总长度,然后减去
class Solution{
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = dummy;
ListNode second = dummy;
int count = 0;
while (first != null && first.next != null) {
first = first.next;
count++;
}
count = count - n;
while (count != 0) {
second = second.next;
count--;
}
second.next = second.next.next;
return dummy.next;
}
}
时间复杂度:O(N),N为链表的长度
空间复杂度:O(1)
题解二
通过dummy
结点,结果返回dummy.next
,避免删除head
结点后返回null
的情况
注意:slow.next = slow.next.next
删除slow.next
结点的方式
class Solution{
public ListNode removeNthFromEnd(ListNode head, int n) {
//ListNode(int val, ListNode next) { this.val = val; this.next = next; }
ListNode dummy = new ListNode(0, head);
ListNode fast = head;
ListNode slow = dummy;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
时间复杂度:O(N)
空间复杂度:O(1)
题解三
快慢指针,通过判断right != null
,来处理删除头结点的情况,举个栗子
输入:[1,2] 2 要求删除结点1
right指针:1->2->null 返回head.next
,返回2
且不用题解二中的dummy结点,更加容易理解,避免出错。
class Solution{
public ListNode removeNthFromEnd2(ListNode head, int n) {
ListNode left = head;
ListNode right = head;
while (n != 0) {
right = right.next;
n--;
}
if (right != null) {
while (right.next != null) {
left = left.next;
right = right.next;
}
left.next = left.next.next;
} else {
return head.next;
}
return head;
}
}
时间复杂度:O(N)
空间复杂度:O(1)
以上是关于算法练习高频「双指针」类型题目总结的主要内容,如果未能解决你的问题,请参考以下文章
❤️导图整理大厂面试高频数组8: 移除元素的双指针优化, 力扣27❤️
思维导图整理大厂面试高频数组24: 合并两个有序数组的两种双指针思想, 力扣88
思维导图整理大厂面试高频数组25: 有序数组的平方的两种双指针思想, 力扣977
思维导图整理大厂面试高频数组25: 有序数组的平方的两种双指针思想, 力扣977