算法入门(LeetCode)——双指针

977. 有序数组的平方

1. 题目

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

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

有序数组的平方

2. 代码
#include <stdio.h>
#include <math.h>
#include <vector>

using namespace std;
class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int n = nums.size();
        vector<int> ans(n);
		int left = 0;
		int right = n - 1;
		int pos = n - 1;
		while (left <= right) {
			if (pow(nums[left],2) > pow(nums[right],2)) {
				ans[pos] = pow(nums[left],2);
				left++;
			} else {
				ans[pos] = pow(nums[right],2);
				right--;
			}
			pos--;
		}
		return ans;
    }
};

时间复杂度O(n), 空间复杂度O(1)

3. 题解
  1. 时间复杂度为 O(n),可得循环体中执行次数为n次,直接循环比较数字的平方,按次序放入, 而不是先平方,后排序
  2. 考虑采用对撞指针,即两个指针分别从数组开头和末尾开始,当left > right 说明数据比较完成,退出循环
  3. 想法是循环找出最大值,依次倒序放入新的vector, 或者循环找出最小值, 依次放入新的vector
    考虑数组可能有正数,有负数,那么找出最小值可能是最左边值或者中间值,若是中间值,就不适用于对撞指针,故排除该方案
    找出最大值,最大值只可能是最左边或最右边,所以采用该方案
  4. 有序数组,若nums[left]^2 > nums[right]^2, 说明left是负数, right是正数, 此时nums[left]^2应为最大值
  5. 有序数组,若nums[left]^2 < nums[right]^2, 说明left,right是正数, 此时nums[right]^2应为最大值
    若nums[left]^2 = nums[right]^2, left , right可能相等,也可能互为相反数,此时nums[right]^2, nums[left]^2都是最大值
  6. 为什么不新增一个比较nums[left]^2 == nums[right]^2, 因为没有必要,仔细看,下一个循环就会将left++ 与 right比较, left+1 会插入就是第二大的平方值,所以没必要多比较一次相等的情况。

189. 轮转数组

1. 题目

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

输入: 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]

轮转数组

2. 代码
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
		int pos = k%(nums.size());
		exchange(nums, 0, nums.size() - 1);
		exchange(nums, 0, pos - 1);
		exchange(nums, pos, nums.size() - 1);
    }
	
	void exchange(vector<int>& nums, int start, int end) {
		while (start < end) {
			int temp = nums[end];
			nums[end] = nums[start];
			nums[start] = temp;
			// std::swap(nums[start], nums[end]);
			start++;
			end--;
		}
	}
};
3. 题解
  1. 看到题目首先想到将nums[i]赋值给nums[(i + k) % nums.size()] ,这种实现就需要新建一个数组用来存放赋值后的数据,再将新数组赋值原数组, 如下
void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> vet(n);
        for (int i = 0; i < n; ++i) {
            vet[(i + k) % n] = nums[i];
        }
        nums.assign(newArr.begin(), newArr.end());
}

此方法new了一个新的vector, 空间复杂度为O(n),要想节约空间,就需要尽可能再原数组上修改,而不是new新数组再赋值

  1. 在原数组修改,就需要数据交换
    可以先翻转数组 7 6 5 4 3 2 1
    发现要想达到最终结果, 只需将[0,k-1] 翻转, [k, nums.size()-1]翻转即可
  2. 注意第二次翻转和第三次翻转的中界位置,如果直接使用k的值,当k > nums.size()时,k -1 > nums.size()-1,这样在调用exchange置换时就会发生数组越界, 所以int pos = k%(nums.size());防止数组越界

283. 移动零

1. 题目

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

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

移动零

2. 代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int left = 0;
		int right = 0;
		while (right < nums.size()) {
			if (nums[right]) {
				std::swap(nums[left], nums[right]);
				left++;
			}
			right++;
        }
    }
};
3. 题解
  1. 采用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部
    右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移
  2. 因为right非0才换置换,所以left自第一次交换后不可能为0

167. 两数之和 II - 输入有序数组

1. 题目

给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:27 之和等于目标数 9 。因此 index1 = 1, index2 = 2 

两数之和 II - 输入有序数组

2. 代码

class Solution {
public:
vector twoSum(vector& numbers, int target) {
vector vet(2);
int left = 0;
int right = numbers.size() - 1;
while (left < right) {
if (numbers[left] + numbers[right] == target) {
vet[0] = left + 1;
vet[1] = right + 1;
return vet;
} else if (numbers[left] + numbers[right] < target) {
left++;
} else if (numbers[left] + numbers[right] > target) {
right–;
}
}
return vet;
}
};

3. 题解
  1. 两两相加,和等于target,首先考虑双指针法
  2. 若相邻两两相加,如何保证每一种组合都被遍历呢,数据有序,何不使用对撞指针,两两相加, 若两数之和==target则return, 两数之和<target,那么left++, 两数之和 > target; right++
  3. while 退出条件应该是找到两数之和==target, 若找不到,则需要两个指针重复时退出

344. 反转字符串

1. 题目

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

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

反转字符串

2. 代码
class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0;
		int right = s.size() - 1;
		while (left < right) {
			// char temp = s[right];
			// s[right] = s[left];
			// s[left] = temp;
            std::swap(s[left], s[right]);
			left++;
			right--;
		}
    }
};
3. 题解

对撞指针反转字符串

557. 反转字符串中的单词 III

1. 题目

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

输入:"Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"

反转字符串中的单词 III

2. 代码
class Solution {
public:
    string reverseWords(string s) {
		int pos = 0;
		int n = s.length();
		while (pos < n) {
			int start = pos;
			while (pos < n && s[pos] != ' ') {
				pos++;
			}
			int end = pos-1;
			reverseStr(s, start, end);
			while (pos < n && s[pos] == ' ') {
				pos++;
			}
		}
		return s;
    }

	void reverseStr(string& s, int start, int end) {
		while (start < end) {
			std::swap(s[start], s[end]);
			start++;
			end--;
		}
    }
};
3. 题解
  1. 反转字符串中每个单词的字符顺序,首先考虑找到每个单词,再将单词翻转
  2. 是单词的条件:s[start] != ’ ’ && s[end] +1 == ’ ’
  3. 最后一个单词: s[start] != ’ ’ && s[end +1] < s.length()
  4. while (pos < n && s[pos] != ’ ') 找到第一个为‘ ’的字符,前一个是单词end
  5. while (pos < n && s[pos] == ’ ‘) 翻转前一个单词后, 找到之后第一个不为’ ’ 的字符即为后一个单词的start

876. 链表的中间结点

1. 题目

给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3(测评系统对该结点序列化表述是 [3,4,5])

链表的中间结点

2. 代码
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

ListNode* middleNode(ListNode* head) {
		// 方法一: 链表无法获取下标,也无法计算长度,可转成数据存储,获取中间节点
//		std::vector<ListNode*> vet;
//		while (head != nullptr) {
//			vet.push_back(head);
//			head = head->next;
//		}
//		return vet[vet.size()/2];
		// 方法二: 先计算链表长度,取链表长度/2的值
//		int size = 0;
//		ListNode* pos = head;
//		while (pos != nullptr) {
//			pos = pos->next;
//			size++;
//		}
//		pos = head;
//		int a = 0;
//		while (a < size/2) {
//			pos = pos->next;
//			a++;
//		}
//		return pos;
		// 方法三: 双指针法, 快慢指针同时出发,慢指针一次走一步, 快指针一次走两步,快指针到达链表末尾,慢指针则在中点
		ListNode* slow = head;
        ListNode* fast = head;
		// 链表长度是双数: fast != nullptr
		// 链表长度是单数: fast->next != nullptr
        while (fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }

	void printList(ListNode* head)
	{
		ListNode* phead=head;
		while(phead!=nullptr)
		{
			cout<<phead->val<<" ";
			phead=phead->next;
		}
		cout<<"\n";
	}		
};

int main(int argc, char **argv)
{
	ListNode* head = new ListNode(1); //不破坏头指针
	ListNode* phead = head;
	for(int i=2;i<=5;i++){
		ListNode* node=new ListNode;
		node->val=i;
		node->next=nullptr;
		phead->next=node;
		phead=node;
	}
	cout<<"链表创建成功!\n";
	Solution so;
	so.printList(head); // 1 2 3 4 5
	ListNode* list = so.middleNode(head);
	so.printList(list); // 3 4 5
	return 0;
}
3. 题解

见代码注释

19. 删除链表的倒数第 N 个结点

1. 题目

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

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

删除链表的倒数第 N 个结点

2. 代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* tmp = new ListNode(0,head);
		ListNode* slow = tmp;
        ListNode* fast = head;
		int i = 0;
		while (i<n && fast != nullptr) {
			fast = fast->next;
			i++;
		}
		while (fast != nullptr) {
			slow = slow->next;
			fast = fast->next;
		}
		slow->next = slow->next->next;
		ListNode* ans = tmp->next;
		delete tmp;
		return ans;
    }
};
3. 题解
  1. 双指针法, 需要找到倒数第个结点的前一个结点,倒数第n个结点前一个依据倒数第一个结点计算, 倒数第n个结点比最后一个结点超前n;也就是间隔n-1个结点
  2. fast指向第n个结点,此时fast 和 slow一起走,直到fast到达链表末尾
  3. fast指向第n个结点,此时fast 和 slow一起走,直到fast到达链表末尾,slow恰好指向倒数第n个结点
  4. 删除结点获取目标结点的前一个结点比较方便,所以ListNode* tmp = new ListNode(0,head),其余操作不变,在删除时删除slow->next结点即可
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值