2. LintCode双指针题目(二)
LintCode 228:链表的中点
https://www.lintcode.com/problem/middle-of-linked-list
描述
找到链表的中点
样例
输入: 1->2->3
输出: 2
样例解释: 返回中间节点的值
输入: 1->2
输出: 1
样例解释: 如果长度是偶数,则返回中间偏左的节点的值。
不重新遍历链表的情况下得到中点
解题思路
这题用快慢指针的思路来做。快指针每次移动两个位置,慢指针每次移动一个位置,当快指针移动到链表尾端时,慢指针就是中点。
AC代码
public ListNode middleNode(ListNode head) {
// 链表为空直接返回null
if (head == null) {
return null;
}
// 快慢指针,都从head开始
ListNode fast = head;
ListNode slow = head;
while (fast.next != null) {
// 快指针先走一步
fast = fast.next;
// 快指针还能走的话,快慢指针都走一步
if (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
}
// 最后返回slow指针
return slow;
}
LintCode 539:移动零
https://www.lintcode.com/problem/move-zeroes
描述
给一个数组 nums 写一个函数将 0
移动到数组的最后面,非零元素保持原数组的顺序
1.必须在原数组上操作
2.最小化操作数
样例
输入: nums = [0, 1, 0, 3, 12],
输出: [1, 3, 12, 0, 0].
输入: nums = [0, 0, 0, 3, 1],
输出: [3, 1, 0, 0, 0].
解题思路
一个思路是将前面出现的0与后面不是0的数字进行交换,但这样不能保证操作数最小。另一个思路是不用管0,因为0都会移到数组的最后面,所以只要我们把非零元素按原来的顺序挪动到数组的前面,后面设置为0就完成了0的移动。用一个index指针遍历数组,一个位置指针表示不为0的数现在应该移动到的位置,index指针数为0不管,不为0就将其移动至位置指针处,位置指针右移。
AC代码
public void moveZeroes(int[] nums) {
// write your code here
// 设置index指针和位置指针初始值,都从0开始
int index = 0, notZero = 0;
while (index < nums.length) {
if (nums[index] != 0) {
nums[notZero++] = nums[index];
}
index++;
}
while (notZero < nums.length) {
nums[notZero++] = 0;
}
}
LintCode 608:两数和II-输入已排序的数组
https://www.lintcode.com/problem/two-sum-ii-input-array-is-sorted
描述
给定一个已经 按升序排列 的数组,找到两个数使他们加起来的和等于特定数。
函数应该返回这两个数的下标,index1必须小于index2。注意返回的值不是 0-based。
你可以假设每个输入刚好只有一个答案
样例
输入: nums = [2, 7, 11, 15], target = 9
输出: [1, 2]
输入: nums = [2,3], target = 5
输出: [1, 2]
解题思路
因为题目保证只有一个答案,所以可以用上篇文字中左右指针的思路,left指针从小的数开始遍历,right指针从大的数开始遍历,两指针指向的和等于target则找到答案;若和大于target则表示两数过大,这时right指针左移(把数变小);若和小于target则表示两数过小,这时left指针右移(把数变大)。
AC代码
public int[] twoSum(int[] nums, int target) {
// 设置左右指针
int left = 0, right = nums.length - 1;
int[] ans = new int[2];
while (left < right) {
int add = nums[left] + nums[right];
// 和为target表示找到答案
if (add == target) {
ans[0] = left + 1;
ans[1] = right + 1;
break;
// 和大于target,right指针左移
} else if (add > target) {
right--;
// 和小于target,left指针右移
} else {
left++;
}
}
return ans;
}
LintCode 609:两数和-小于或等于目标值
https://www.lintcode.com/problem/two-sum-less-than-or-equal-to-target
描述
给定一个整数数组,找出这个数组中有多少对的和是小于或等于目标值。返回对数
样例
输入: nums = [2, 7, 11, 15], target = 24.
输出: 5.
解释:
2 + 7 < 24
2 + 11 < 24
2 + 15 < 24
7 + 11 < 24
7 + 15 < 24
输入: nums = [1], target = 1.
输出: 0.
解题思路
上题的变种,思路一致,稍微处理一下即可。两数之和大于target时,right一直左移直至小于等于,然后此时left指针和right指针之间的数(包括right指针)之和都小于等于target,所以答案个数加上right-left,然后left指针右移。
AC代码
public int twoSum5(int[] nums, int target) {
// 少于2个数直接返回0
if (nums.length < 2) {
return 0;
}
// 按升序排序
Arrays.sort(nums);
// 设置左右指针
int left = 0, right = nums.length - 1;
int ans = 0;
while (left < right) {
// 两数和大于target时,right左移直至小于等于target
while (left < right && nums[left] + nums[right] > target) {
right--;
}
// 此时left指针和right指针之间的数(包括right指针)之和都小于等于target
if (left < right) {
ans += right - left;
}
// left指针右移
left++;
}
// 返回对数
return ans;
}
LintCode 533:两数和的最接近值
https://www.lintcode.com/problem/two-sum-closest-to-target
描述
给定整数数组num
,从中找到两个数字使得他们和最接近target
,返回两数和与 target
的差的 绝对值
。
样例
输入: nums = [-1, 2, 1, -4] 并且 target = 4
输出: 1
解释:
最小的差距是1,(4 - (2 + 1) = 1).
输入: nums = [-1, -1, -1, -4] 并且 target = 4
输出: 6
解释:
最小的差距是6,(4 - (- 1 - 1) = 6).
解题思路
还是之前两数和的变种题,用左右指针不断靠近target,更新最小差值即可。
AC代码
public int twoSumClosest(int[] nums, int target) {
// 按升序排序
Arrays.sort(nums);
// 设置左右指针
int left = 0, right = nums.length - 1;
int ans = Integer.MAX_VALUE;
while (left < right) {
int add = nums[left] + nums[right];
// 两数和为target时,直接返回0
if (add == target) {
return 0;
}
// 和小于target,left右移,不断逼近target
if (add < target) {
left++;
}
// 和大于target,right左移,不断逼近target
else {
right--;
}
// 比较最小差值
ans = Math.min(ans, Math.abs(add - target));
}
// 返回差值
return ans;
}
LintCode 443:两数之和 II
https://www.lintcode.com/problem/two-sum-greater-than-target
描述
给一组整数,问能找出多少对整数,他们的和大于一个给定的目标值
O(1)额外空间以及(nlogn)时间复杂度
样例
输入: [2, 7, 11, 15], target = 24
输出: 1
解释: 11 + 15 是唯一的一对
输入: [1, 1, 1, 1], target = 1
输出: 6
解题思路
前面小于等于目标值的翻版题目
AC代码
public int twoSum2(int[] nums, int target) {
// 排序
int ans = 0;
Arrays.sort(nums);
// 设置左右指针
int left = 0, right = nums.length - 1;
// 遍历数组
while (left < right) {
int add = nums[left] + nums[right];
// 和小于target,left指针右移
if (add <= target) {
left++;
} else {
// 和大于targt,那么从left到right间的数(包括left)与right指针的和都大于target
// right指针左移
ans += right - left;
right--;
}
}
// 返回答案
return ans;
}
LintCode 461:无序数组K小元素
https://www.lintcode.com/problem/kth-smallest-numbers-in-unsorted-array
描述
找到一个无序数组中第K小的数
O(nlogn)的算法固然可行,但如果你能O(n)解决,那就非常棒了。
样例
输入: [3, 4, 1, 2, 5], k = 3
输出: 3
输入: [1, 1, 1], k = 2
输出: 1
解题思路
快速选择的思路,跟快排中partition部分一样,随机选择数组中的一个数t,将小于t的数都放至左边,大于t的数放至右边,这样如果左边数的个数大于等于K,则K小元素在左半部分,递归查找左半部分;反之查找右半部分。
AC代码
public int kthSmallest(int k, int[] nums) {
// 数组从0开始所以把k-1
return partition(nums, 0, nums.length - 1, k - 1);
}
// 快速选择,和快排中的partition类似
private int partition(int[] nums, int start, int end, int k) {
if (start == end) {
return nums[start];
}
// 从左右两端开始
int left = start, right = end;
int mid = nums[left + (right - left) / 2];
while (left < right) {
// 从左指针扫描找到比mid大的数
while (left <= right && nums[left] < mid) {
left++;
}
// 从右指针扫描找到比mid小的数
while (left <= right && nums[right] > mid) {
right--;
}
// 进行交换
if (left <= right) {
int tmp = nums[left];
nums[left++] = nums[right];
nums[right--] = tmp;
}
}
// 进行partition操作后,left 要么等于 right + 1,要么等于right + 2
// start到right的数小于 mid,left到end的数大于mid
// 如果right >= k 则表示 左半部分的数至少有 k 个,所以在start 和 right 中查找答案
if (right >= k && start <= right) {
return partition(nums, start, right, k);
}
// 如果left <= k 则表示 左半部分的数少于 k 个,所以要去left 和 end 中查找答案
if (left <= k && left <= end) {
return partition(nums, left, end, k);
}
// 上述条件都不满足,则表明当前k的位置便是答案
return nums[k];
}
LintCode 382:三角形计数
https://www.lintcode.com/problem/triangle-count
描述
给定一个整数数组,在该数组中,寻找三个数,分别代表三角形三条边的长度,问,可以寻到多少组这样的三个数来组成三角形?
样例
输入: [3, 4, 6, 7]
输出: 3
解释:
可以组成的是 (3, 4, 6),
(3, 6, 7),
(4, 6, 7)
输入: [4, 4, 4, 4]
输出: 4
解释:
任何三个数都可以构成三角形
所以答案为 C(3, 4) = 4
解题思路
前面的443题是在数组中找出有多少组两个数的和大于一个给定的值,而三角形的三条边应满足的条件便是两边之和大于第三边,正好和该题要求的问题一致,所以可以将数组排序,然后从第三个数开始遍历,然后求该数前面的数中能找出多少组两数之和大于它。
AC代码
public int triangleCount(int[] S) {
// 排序
Arrays.sort(S);
int ans = 0;
// 从第三个数开始枚举
for (int i = S.length - 1; i > 1 ; i--) {
// 左指针从0开始,右指针从当前数的前一位开始
int left = 0, right = i - 1;
while (left < right) {
// 两数之和大于答案加一,right指针左移
if (S[left] + S[right] > S[i]) {
ans += right - left;
right--;
} else {
// 两数之和小于,left指针右移
left++;
}
}
}
return ans;
}
LintCode 144:交错正负数
https://www.lintcode.com/problem/interleaving-positive-and-negative-numbers
描述
给出一个含有正整数和负整数的数组,重新排列成一个正负数交错的数组。
不需要保持正整数或者负整数原来的顺序。完成题目,且不消耗额外的空间。
样例
输入 : [-1, -2, -3, 4, 5, 6]
输出 : [-1, 5, -2, 4, -3, 6]
解释 : 或者任何满足条件的答案
解题思路
一个简单的思路就是把负数集中起来,正数集中起来(可以用快速选择实现,也就是前面题目中的partition部分),然后交替拜访即可。(负数多先放负数,正数多先放正数)
AC代码
public void rerange(int[] A) {
// 首先将小于0的数移动到左边,大于0的数移动到右边
int left = 0, right = A.length - 1;
while (left < right) {
while (left < right && A[left] < 0) {
left++;
}
while (left < right && A[right] > 0) {
right--;
}
if (left < right) {
int tmp = A[left];
A[left++] = A[right];
A[right--] = tmp;
}
}
// 因为负数都在左边,正数都在右边,所以隔一个负数就和正数进行一次交换,就能满足正负排列
// 负数从0开始,正数从最后开始
int start = 0, end = A.length - 1;
// 正数个数,left为负数个数
int posCount = A.length - left;
// 负数多,那从负数的第二个开始交换,最后队列是负正负正。。。。负正负。
if (posCount < left) {
start++;
// 正数多,从负数的第一个和正数的第二个(从右往左)开始交换,最后队列是正负正负。。。。正负正。
} else if (posCount > left) {
end--;
// 正负一样多,从负数的第二个和正数的第二个(从右往左)开始交换,最后队列是负正负正。。。。负正负正。
} else {
start++;
end--;
}
// 交换一个跳一格继续交换,直到交换完成
while (start < end) {
int tmp = A[start];
A[start] = A[end];
A[end] = tmp;
start += 2;
end -= 2;
}
}