2. LintCode双指针题目(二)

2. LintCode双指针题目(二)

1. 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;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值