C++力扣Leetcode算法2--双指针

目录

167 在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。

88 合并数组

142 链表环

76 字符串

633 平方数之和


双指针遍历方向相同且不相交,称为滑动窗口,通常用于区间搜索

双指针遍历方向相反,用来搜索,待搜数组通常是排好序的

指针函数,返回类型是指针的函数

函数指针,指向函数的指针

快慢指针通常用于链表环,一个指针走一步,一个指针走两步,快指针走到尽头就是没环路,走到第一次相遇,快指针从头走,走一步,第二次相遇,就是环点

167 在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列  ,
请你从数组中找出满足相加之和等于目标数 target 的两个数。
如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
解:1.排好序的数组,双指针,一个指最右,一个指最左。
    2.相加与值相比较,小于移左边指针,大于移右边指针
书中最优解

vector<int> pointers::twoSum(vector<int>& numbers, int target)
{
	int l = 0, r = numbers.size() - 1, sum;
	while (l < r) {
		sum = numbers[l] + numbers[r];
		if (sum == target) break;
		if (sum < target) ++l;
		else --r;
	}
	return vector<int>{l + 1, r + 1};
	
}

88 合并数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,
分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。
为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,
后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
解:1.两个指针分别放在两个数组的末尾,m n
    2.每次将较大的那个数字复制到 nums1 的后边,然后向前移动一位。
    3.个 pos 指针,起始位置为 m +n−1。
书中最优解

void pointers::merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
{
	int pos = m-- + n-- - 1;
	while (m >= 0 && n >= 0) {
		nums1[pos--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
	}
	while (n >= 0) {
		nums1[pos--] = nums2[n--];
	}

}

答案有很多种

本人写 超出时间限制
class Solution {
public:
	void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
		nums1.insert(nums1.end(), nums2.begin(), nums2.end());
		sort(nums1.begin(), nums1.end());
		for (int i = 0; i < nums1.size()-1; ++i) {
			if (nums1[i] == 0)
				nums1.erase(nums1.begin());
				if(i>0)  --i;
		}
	}
};

官方答案
class Solution {
public:
	void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
		for (int i = 0; i != n; ++i) {
			nums1[m + i] = nums2[i];
		}
		sort(nums1.begin(), nums1.end());
	}
};
网页答案
class Solution {
public:
	void merge(vector<int> &nums1, int m, vector<int> &nums2, int n) {
		int p1 = m - 1, p2 = n - 1, p = m + n - 1;
		while (p2 >= 0) { // nums2 还有要合并的元素
			// 如果 p1 < 0,那么走 else 分支,把 nums2 合并到 nums1 中
			if (p1 >= 0 && nums1[p1] > nums2[p2]) {
				nums1[p--] = nums1[p1--]; // 填入 nums1[p1]
			} else {
				nums1[p--] = nums2[p2--]; // 填入 nums2[p1]
			}
		}
	}
};

142 链表环

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 
为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
解:对于链表找环路的问题,有一个通用的解法——快慢指针(Floyd 判圈法)。给定两个指针,
分别命名为 slow 和 fast,起始位置在链表的开头。每次 fast 前进两步,slow 前进一步。如果 fast
可以走到尽头,那么说明没有环路;如果 fast 可以无限走下去,那么说明一定有环路,且一定存
在一个时刻 slow 和 fast 相遇。当 slow 和 fast 第一次相遇时,我们将 fast 重新移动到链表开头,并
让 slow 和 fast 每次都前进一步。当 slow 和 fast 第二次相遇时,相遇的节点即为环路的开始点
加深理解:
https://leetcode.cn/problems/linked-list-cycle-ii/solutions/441131/huan-xing-lian-biao-ii-by-leetcode-solution/comments/1372178

书中最优解

ListNode* pointers::detectCycle(ListNode* head)
{
	ListNode* slow = head, * fast = head;
	// 判断是否存在环路
	do {
		if (!fast || !fast->next) return nullptr;
		fast = fast->next->next;
		slow = slow->next;
	} while (fast != slow);
	// 如果存在,查找环路节点
	fast = head;
	while (fast != slow) {
		slow = slow->next;
		fast = fast->next;
	}
	return fast;
}

76 字符串

给定两个字符串 S 和 T,求 S 中包含 T 所有字符的最短连续子字符串的长度,同时要求时间
复杂度不得超过 O(n)。
解:1.指针遍历方向相同但不相交,滑动窗口
    2.判断窗口内字符串是否包含t
书中最优解

string pointers::minWindow(string s, string t)
{
	vector<int> chars(128, 0);
	vector<bool> flag(128, false);
	// 先统计T中的字符情况
	for (int i = 0; i < t.size(); ++i) {
		flag[t[i]] = true;
		++chars[t[i]];
	}
	// 移动滑动窗口,不断更改统计数据
	int cnt = 0, l = 0, min_l = 0, min_size = s.size() + 1;
	for (int r = 0; r < s.size(); ++r) {
		if (flag[s[r]]) {
			if (--chars[s[r]] >= 0) {
				++cnt;
			}
			// 若目前滑动窗口已包含T中全部字符,
			// 则尝试将l右移,在不影响结果的情况下获得最短子字符串
			while (cnt == t.size()) {
				if (r - l + 1 < min_size) {
					min_l = l;
					min_size = r - l + 1;
				}
				if (flag[s[l]] && ++chars[s[l]] > 0) {
					--cnt;
				}
				++l;
			}
		}
	}
	return min_size > s.size() ? "" : s.substr(min_l, min_size);
}

网页答案

链接:
https://leetcode.cn/problems/minimum-window-substring/solutions/257359/zui-xiao-fu-gai-zi-chuan-by-leetcode-solution/comments/409075

class Solution {
	public String minWindow(String s, String t) {
		if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) {
			return "";
		}
		//维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
		//ASCII表总长128
		int[] need = new int[128];
		int[] have = new int[128];

		//将目标字符串指定字符的出现次数记录
		for (int i = 0; i < t.length(); i++) {
			need[t.charAt(i)]++;
			//charAt()方法返回指定索引位置的char值。
		}

		//分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
		//已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
		int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
		while (right < s.length()) {
			char r = s.charAt(right);
			//说明该字符不被目标字符串需要,此时有两种情况
			// 1.循环刚开始,那么直接移动右指针即可,不需要做多余判断
			// 2.循环已经开始一段时间,此处又有两种情况
			//  2.1 上一次条件不满足,已有字符串指定字符出现次数不满足目标字符串指定字符出现次数,那么此时
			//      如果该字符还不被目标字符串需要,就不需要进行多余判断,右指针移动即可
			//  2.2 左指针已经移动完毕,那么此时就相当于循环刚开始,同理直接移动右指针
			if (need[r] == 0) {
				right++;
				continue;
			}
			//当且仅当已有字符串目标字符出现的次数小于目标字符串字符的出现次数时,count才会+1
			//是为了后续能直接判断已有字符串是否已经包含了目标字符串的所有字符,不需要挨个比对字符出现的次数
			if (have[r] < need[r]) {
				count++;
			}
			//已有字符串中目标字符出现的次数+1
			have[r]++;
			//移动右指针
			right++;
			//当且仅当已有字符串已经包含了所有目标字符串的字符,且出现频次一定大于或等于指定频次
			while (count == t.length()) {
				//挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
				if (right - left < min) {
					min = right - left;
					start = left;
				}
				char l = s.charAt(left);
				//如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
				if (need[l] == 0) {
					left++;
					continue;
				}
				//如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
				//就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
				if (have[l] == need[l]) {
					count--;
				}
				//已有字符串中目标字符出现的次数-1
				have[l]--;
				//移动左指针
				left++;
			}
		}
		//如果最小长度还为初始值,说明没有符合条件的子串
		if (min == s.length() + 1) {
			return "";
		}
		//返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
		return s.substring(start, start + min);
	}
}

633 平方数之和

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a*a + b*b = c 。
官方答案

bool pointers::judgeSquareSum(int c) 
{
	long left = 0;
	long right = (int)sqrt(c);
	while (left <= right) {
		long sum = left * left + right * right;
		if (sum == c) {
			return true;
		}
		else if (sum > c) {
			right--;
		}
		else {
			left++;
		}
	}
	return false;
}
本人写,提交超出时间限制
bool pointers::judgeSquareSum(int c)
{
	for (int a = 0; a < c / 2; ++a) {
		int  b = c;
		while ((a * a + b * b) != c && b >= 0) {
			--b;
			if (b < 0) b = b + 1;
		}if ((a * a + b * b) == c) return true;
	}
	return false;

}
bool pointers::judgeSquareSum(int c) {
	int a = 0;
	int b = c;
	while (a < b) {
		if ((a * a + b * b) == c) {
			return true;
		}
		else if ((a * a + b * b) > c) {
			b--;
		}
		else
			a++;

	}
	return false;
}

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值