算法刷题--双指针

Code 1 : Squares of a Sorted Array

Given an integer array nums sorted in non-decreasing order, return an array of the squares of each number sorted in non-decreasing order.

Example 1

Input: nums = [-4,-1,0,3,10]
Output: [0,1,9,16,100]
Explanation: After squaring, the array becomes [16,1,0,9,100].
After sorting, it becomes [0,1,9,16,100].

Example 2

Input: nums = [-7,-3,2,3,11]
Output: [4,9,9,49,121]

Solution

  • 冒泡排序
class Solution {
    
    
public:
    vector<int> sortedSquares(vector<int>& nums) {
    
    
    	for (int i=0;i<nums.size();i++){
    
    
    		nums[i]=(nums[i]*nums[i]);
		}
		for (int i=0;i<nums.size();i++){
    
    
			for(int j=0;j<nums.size()-i-1;j++){
    
    
				if (nums[j]>nums[j+1]){
    
    
					int temp=nums[j];
					nums[j]=nums[j+1];
					nums[j+1]=temp;
				}
			}
		}
		return nums;
		
    }
};

注意冒泡排序的内部循环。

for (int i=0;i<nums.size();i++){
    
    
			for(int j=0;j<nums.size()-i-1;j++){
    
    
				if (nums[j]>nums[j+1]){
    
    
					int temp=nums[j];
					nums[j]=nums[j+1];
					nums[j+1]=temp;
				}
			}
		}

与以下的选择排序作对比。

  • 选择排序
vector<int> sortedSquares(vector<int>& nums) {
    
    
    	for (int i=0;i<nums.size();i++){
    
    
    		nums[i]=(nums[i]*nums[i]);
		}
		for (int i=0;i<nums.size();i++){
    
    
			for(int j=i+1;j<nums.size();j++){
    
    
				if (nums[j]<nums[i]){
    
    
					int temp=nums[j];
					nums[j]=nums[i];
					nums[i]=temp;
				}
			}
		}
		for (int i=0;i<nums.size();i++)
			cout<<nums[i]<<' ';
		return nums;	
    }

j遍历i后的全部元素,如果j对应的值比i对应的值要大,就交换i和j。
循环部分:

for (int i=0;i<nums.size();i++){
    
    
			for(int j=i+1;j<nums.size();j++){
    
     //这里有区别
				if (nums[j]<nums[i]){
    
       
					int temp=nums[j];
					nums[j]=nums[i];
					nums[i]=temp;
				}
			}
		}

对于这道题,选择排序和冒泡排序都会超时。

  • 直接插入法
class Solution {
    
    
public:
    //直接插入排序 
     vector<int> sortedSquares(vector<int>& nums) {
    
    
    	int n=nums.size();
    	if(n==0) return nums;
    	
    	nums[0]=nums[0]*nums[0]; //改变第一个 
    
    	for(int i=1;i<n;i++){
    
    
    		
    		int temp=nums[i]*nums[i];  //找到要插入元素的对应值
    		
    		int j=i;
    		while(j>0 and temp<=nums[j-1]){
    
      //找到插入位置
    			nums[j]=nums[j-1];    //插入位置后每个元素往后移一个(可以覆盖掉要插入的元素)
    			j--;
			}
			nums[j]=temp;   //相应地方的赋值
		}
		return nums;   
    }
};

直接插入是在前面的排序已经完成的情况下进行的。插入时定位到插入位置同时将其余元素后移(不用动插入元素后面的)。

  • 折半查找
 vector<int> sortedSquares(vector<int>& nums) {
    
    
    	if (nums.size()==0) return nums;
    	nums[0]=nums[0]*nums[0];
    	for (int i=1;i<nums.size();i++){
    
    
    		int temp=nums[i]*nums[i];
			int l=0;
			int r=i-1;
			while (l<=r){
    
    
				int mid=(l+r)/2;
				if(nums[mid]<=temp){
    
    
					l=mid+1;
				}
				else{
    
    
					r=mid-1;
				}
			}
			for(int j=i;j>r;j--){
    
    
				nums[j]=nums[j-1];
			}
			nums[l]=temp;
		}
		return nums;
		
    }

有点类似于二分查找。

  • 双指针
class Solution {
    
    
public:
  
	 vector<int> sortedSquares(vector<int>& nums) {
    
    
	 	int i=0,j=nums.size()-1;
	 	vector<int> result(nums.size());
	 	int r=result.size()-1;
	 	while(i<=j){
    
    
	 		if(nums[i]*nums[i]<=nums[j]*nums[j]){
    
    
	 			result[r]=nums[j]*nums[j];
	 			cout<<result[r]<<endl;
	 			j--;
	 			r--;
			 }
			 else{
    
    
	 			result[r]=nums[i]*nums[i];
	 			i++;
	 			r--;
			 }
		 }
		
		 return result;
	 	
	 }
};

本题给出的数组是有序的,需要多考虑的是负数的部分,发现平方后最大值都在两边,中间的绝对值最小。考虑双指针法,使i指向起始位置,j指向终止位置。通过移动i和j来将数组从大到小插入result

Code 2 : Rotate Array

Given an array, rotate the array to the right by k steps, where k is non-negative.

Example 1

Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
rotate 1 steps to the right: [7,1,2,3,4,5,6]
rotate 2 steps to the right: [6,7,1,2,3,4,5]
rotate 3 steps to the right: [5,6,7,1,2,3,4]

Example 2

Input: nums = [-1,-100,3,99], k = 2
Output: [3,99,-1,-100]
Explanation: 
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]

Solution

  • 反转三次
class Solution {
    
    
public:
    void rotate(vector<int>& nums, int k) {
    
    
    	reverse(nums,0,nums.size()-1);
    	reverse(nums,0,k%nums.size()-1);
    	reverse(nums,k%nums.size(),nums.size()-1);
    }
    
    void reverse(vector<int>& nums,int start,int end){
    
    
    	int i=start,j=end;
    	while(i<=j){
    
    
    		int temp=nums[i];
    		nums[i]=nums[j];
    		nums[j]=temp;
    		i++;
    		j--;
		}
	}
};

假设原vector是 1234567, 要求移动三次, 结果应该是5671234
这种方法把1234567分成了两段,先将全部反转,变为7654321,然后以三个数为边界分成两段 765 4321 再将其分别反转567 1234
其中在旋转函数中用到了双指针。
注意k%nums.size()来处理k大于nums的长度的问题。

  • 使用额外空间
class Solution {
    
    
public:
    void rotate(vector<int>& nums, int k) {
    
    
    	vector<int> tool(nums.size());
    	
    	for(int i=0;i<nums.size();i++){
    
    
    		tool[(i+k)%nums.size()]=nums[i];
		}
		
		nums.assign(tool.begin(),tool.end()); 
    }
  
};

这里注意理解tool[(i+k)%nums.size()]=nums[i];的意思。
cpp中assign()函数的作用是顺次地把一个 string 对象的部分内容拷贝到另一个 string 对象上。

Code 3 : Move Zeros

Given an integer array nums, move all 0’s to the end of it while maintaining the relative order of the non-zero elements.

Note that you must do this in-place without making a copy of the array.

Example 1

Input: nums = [0,1,0,3,12]
Output: [1,3,12,0,0]

Example 2

Input: nums = [0]
Output: [0]

Solution

  • 双指针(从两端逼近,较复杂)
class Solution {
    
    
public:
    void moveZeroes(vector<int>& nums) {
    
    
    	int i=0;
    	int j=nums.size()-1;
    	while(nums[j]==0 && j>=i){
    
    
    		j--;
		}
		while(i<j){
    
    
			if(nums[i]==0){
    
    
				for(int k=i;k<j;k++){
    
    
					nums[k]=nums[k+1];
				}
				nums[j]=0;
				j--;
			}
			else{
    
    
				i++;
			}
		}
    }
    
};
  • 双指针改进版

class Solution {
    
    
public:
    void moveZeroes(vector<int>& nums) {
    
    
    	int s=0,f=0;
    	while(f<nums.size()){
    
    
    		if(nums[f]){
    
    
    			swap(nums[s],nums[f]);
    			s++;
			}
			f++;
		}
		
		for(int i=0;i<nums.size();i++){
    
    
			cout<<nums[i]<<' ';
		}
    }
    
};

这种方法实际上是一直让较慢的指针停留在0的位置,当快的指针不为零的时候就把二者交换,把0换到后面去。

Code 4 : Reverse String

Write a function that reverses a string. The input string is given as an array of characters s.
Example 1

Input: s = ["h","e","l","l","o"]
Output: ["o","l","l","e","h"]

Example 2

Input: s = ["H","a","n","n","a","h"]
Output: ["h","a","n","n","a","H"]

Solution

  • 双指针
class Solution {
    
    
public:
    void reverseString(vector<char>& s) {
    
    
		int l=0,r=s.size()-1;
		while(l<r){
    
    
			swap(s[l],s[r]);
            l++;
            r--;
		}
    }
};

具体思路在移动数组那一题有交叉的地方。

Code 5

Solution

  • 双指针(调用外部函数)
class Solution {
    
    
public:
    string reverseWords(string s) {
    
    
		int pt=0;
		int start=0;
		for(int i=0;i<s.size();i++){
    
    
			if(s[i]==' '){
    
    
				reverse(s,start,i-1);
				i++;
				start=i;
			}
			if(i==s.size()-1){
    
    
				reverse(s,start,i);
			}
		}
        return s;
    }
    
    void reverse(string &s,int start,int end){
    
    
    	while(start<end){
    
    
    		swap(s[start],s[end]);
    		start++;
    		end--;
		}
	}
};

也是用到了之前的reverse()的思路,遇到空格的就反转前一个词,最后一个词特殊处理。

  • 双指针(不调用外部函数)
class Solution {
    
    
public:
    string reverseWords(string s) {
    
    
    	int p=0;
    	while(p<s.size()){
    
    
    		int start=p;
    		while(s[p]!=' '&&p<s.size()){
    
    
    			p++;
			}
			int l=start,r=p-1;
			while(l<r){
    
    
				swap(s[l],s[r]);
				l++;
				r--;		
			}
			while(s[p]==' '&&p<s.size()){
    
    
    			p++;
			}
			
		}
		return s;	
    }   
};

这种方法较前面那个好一点,不用特意考虑最后一种情况。p在每个单词末尾或者末尾的空格停下然后反转。

  • 使用额外的空间

class Solution {
    
    
public:
    string reverseWords(string s) {
    
    
    	string reS;
    	int p=0;
    	while(p<s.size()){
    
    
    		int start=p;
    		while(s[p]!=' '&&p<s.size()){
    
    
    			p++;
			}
			for(int i=p-1;i>=start;i--){
    
    
				reS.push_back(s[i]);
			}
			
			while(s[p]==' '&&p<s.size()){
    
    
    			p++;
    			reS.push_back(' ');
			}
		}
		return reS;
    }
    
};

想法和上一个相同,但反转每个单词的时候不太一样,用到了额外的空间,性能不高。
注意添加空格的时候要在最后一个while里添加。

Code 6 : Middle of the Linked List

Given the head of a singly linked list, return the middle node of the linked list.
If there are two middle nodes, return the second middle node.
Example 1

Input: head = [1,2,3,4,5]
Output: [3,4,5]
Explanation: The middle node of the list is node 3.

Example 2

Input: head = [1,2,3,4,5,6]
Output: [4,5,6]
Explanation: Since the list has two middle nodes with values 3 and 4, we return the second one.

Solution

  • 快慢双指针(自己写的不是很清晰) 可以和下面的方法对比
class Solution {
    
    
public:
    ListNode* middleNode(ListNode* head) {
    
    
    	ListNode* slow=head;
    	ListNode* fast=head;
    	while(fast){
    
    
            if(fast->next) fast=fast->next;
            else return  slow;

            if(fast->next) fast=fast->next;
            else return  slow->next;

    		slow=slow->next;
              		
		}
		return slow;	
    }
};
  • 双指针简洁版
class Solution {
    
    
public:
    ListNode* middleNode(ListNode* head) {
    
    
    	ListNode* slow=head;
    	ListNode* fast=head;
    	while(fast && fast.next){
    
    
    		slow=slow->next;
    		fast=fast->next->next;
		}
		return slow;	
    }
}
  • 另一种方法是用单指针遍历后返回中间的,比较复杂。

Code 7 : Tow Sum

Given an array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number.

Return the indices of the two numbers (1-indexed) as an integer array answer of size 2, where 1 <= answer[0] < answer[1] <= numbers.length.

The tests are generated such that there is exactly one solution. You may not use the same element twice.

Example 1

Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: The sum of 2 and 7 is 9. Therefore index1 = 1, index2 = 2.

Example 2

Input: numbers = [2,3,4], target = 6
Output: [1,3]

Example 3

Input: numbers = [-1,0], target = -1
Output: [1,2]

Solution

  • 双指针
class Solution {
    
    
public:
   vector<int> twoSum(vector<int>& numbers, int target) {
    
    
   	int l=0,r=numbers.size()-1;
   	while(l<r){
    
    
   		if(numbers[l]+numbers[r]>target) r--;
   		else if (numbers[l]+numbers[r]<target) l++;
           else{
    
    
   			vector<int> a={
    
    l+1,r+1};
   			return a;
   		}
   	}
   	return vector<int>();
   }
   
};

利用vector是顺序排列的特性,规定左右两个指针,如果比目标结果大了就移动右指针(减小),比目标小就移动左指针(增大)。

Code 8 : Remove Nth Node From End of List

Given the head of a linked list, remove the nth node from the end of the list and return its head.

Example 1

Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]

Example 2

Input: head = [1], n = 1
Output: []

Example 3

Input: head = [1,2], n = 1
Output: [1]

Solution

  • 双指针(快慢指针)
class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
    	ListNode* dummy = new ListNode(0, head);
    	ListNode* first=head;
    	ListNode* second=dummy;
    	for(int i=0;i<n;i++){
    
    
    		first=first->next;
		}
		while(first){
    
    
			second=second->next;
			first=first->next;
		}
		second->next=second->next->next;
		return dummy->next;
    }
};

快慢指针相差n个节点,当快节点到尽头的时候,慢节点刚好是要删除的前一个。
一般在处理链表问题时,可以考虑加入哑节点,它的 next 指针指向链表的头节点,这就不需要对头节点进行特殊的判断了。

猜你喜欢

转载自blog.csdn.net/yxyxxxyyyy/article/details/120075658