文章目錄
- 1. [LeetCode01:two sum](https://leetcode.com/problems/two-sum/)
- 2. [LintCode607. Two Sum III - Data structure design](https://www.lintcode.com/problem/two-sum-iii-data-structure-design/my-submissions)
- 3. 167. [Two Sum II - Input array is sorted](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/)
- 4. LintCode587.Two Sum - Unique pairs
- 5. [LeetCode15. 3Sum](https://leetcode.com/problems/3sum/)
- 題意
- 思路
- 代碼
- 6. [LintCode-382. Triangle Count](https://www.lintcode.com/problem/triangle-count/description)
- 題意
- 思路
- 代碼
- 7. [LintCode59. 3Sum Closest](https://www.lintcode.com/problem/3sum-closest/description)
- [8. LeetCode18-4Sum](https://leetcode.com/problems/4sum/)
這類題目都是套路非常明顯的題目,掌握下述的題目後應該對這類問題沒有任何問題。
1. LeetCode01:two sum
題意
- 給定數組和一個目標數,要求在數組中找到兩個數,其和等於目標數,返回該兩數的下標
思路1:map法
- 建立從
value->index
的映射 - 遍歷數組
- 依次檢驗當前
map
的key
中是否包含target - num[i]
- 如果包含,那麼說明一定存在
nums[j] = target - nums[i]
已經在map
中,且一定j<i
,那麼只需要通過targte - nums[i]
在map
中找到下標j
,那麼j和i
就是結果 - 如果不包含,則把當前的
nums[i]和i
添加到map
中即可
- 如果包含,那麼說明一定存在
- 依次檢驗當前
代碼1:
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if (nums == null || nums.length == 0){
return null;
}
//0. num->index
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])){
res[0] = map.get(target - nums[i]);
res[1] = i;
return res;
}
map.put(nums[i],i);
}
return res;
}
思路2:雙指針解法
- 在有序數組中可以利用兩個相向雙指針,一個從左往右,一個從右往左的遍歷數組,然後求出題目要求(兩數之和等於/大於/小於/最接近於某個數)的答案即可
- 所以雙指針解法的第一步永遠是排序
- 如果題目要求返回的值是索引,此時如果直接對數組進行排序,那麼就會丟失掉題目給定數組中數組元素和索引的對應關係,此時通用的解決辦法是利用一個
Pair
來存儲value和index
的對應關係,然後對Pair
的value
進行排序 - 當得到有序數組後,雙指針的邏輯就很簡單了(見代碼中的註釋部分)
代碼2
class Solution {
class Pair{
int val;
int index;
public Pair(int val, int index){
this.val = val;
this.index = index;
}
}
public int[] twoSum(int[] nums, int target) {
if (nums == null || nums.length == 0){
return null;
}
int[] res = new int[2];
Pair[] pairs = new Pair[nums.length];
//0. 將數組元素值和index進行綁定
for (int i = 0; i < nums.length; i++) {
pairs[i] = new Pair(nums[i],i);
}
//1. 雙指針解法的第一步永遠是排序
Arrays.sort(pairs,new Comparator<Pair>(){
@Override
public int compare(Pair o1, Pair o2) {
return o1.val - o2.val;
}
});
int left = 0;
int right = nums.length - 1;
while (left < right){
if (pairs[left].val + pairs[right].val == target){
res[0] = pairs[left].index;
res[1] = pairs[right].index;
return res;
}else if (pairs[left].val + pairs[right].val > target){
//2. 當前的最大值加上最小值 都還是大於目標值,說明最大值太大了
right--;
}else {
//3.當前的最小值加上最大值 都還是小於目標值,說明最小值太小了
left++;
}
}
return null;
}
}
小結
- 上述兩種思路是解決兩數之和這類問題的通用解決手段
2. LintCode607. Two Sum III - Data structure design
題意
- 實現一個數據結構,完成如下兩個功能
void add(num)
:可以增加給定的數據boolean find(value)
:查找當前數據集中是否存在兩數之和等於value
思路
- 首先,該數據結構需要存儲數據,在設計數據結構的時候,底層的數據存儲要麼用鏈表要麼用動態數組
- 此題中並沒有說明要存儲數據的具體長度,那麼就只能用動態數組,在
Java
中最常用的就是list
- 其次,第二個功能的實現,就是一個兩數之和問題,而且此題不要求返回兩數的索引,只需要判斷是否存在,那麼顯然考慮用
map
法,因爲完全不需要考慮索引,完全可以把map
簡化爲用一個set
來做判斷
代碼
class Solution {
//0. 用list來存數據
List<Integer> list = new ArrayList<>();
public void add(int number) {
// write your code here
list.add(number);
}
public boolean find(int value) {
// write your code here
Set<Integer> set = new HashSet<>();
for (Integer num : list) {
if (set.contains(value - num)){
return true;
}
set.add(num);
}
return false;
}
}
3. 167. Two Sum II - Input array is sorted
題意
給定有序數組,和一目標數,在數組中找到兩數之和等於目標數,返回兩數在數組中的次序(注意不是基0的索引,所以最後需要+1)
思路
- 是第1題的簡化版
- 因爲已經有序了,不需要再排序,自然也不需要處理排序後數組值和索引無法對應的問題
- 直接用雙指針來處理即可
代碼
public static int[] twoSum(int[] A, int target) {
int[] res = new int[2];
int left = 0;
int right = A.length - 1;
while (left < right){
if (A[left] + A[right] == target){
res[0] = left + 1;
res[1] = right + 1;
return res;
}else if (A[left] + A[right] > target){
//1. 和大了 要減小
right--;
}else {
//2. 和小了 要增大
left++;
}
}
return null;
}
4. LintCode587.Two Sum - Unique pairs
此題是vip,無法提供鏈接
題意
- 給定一個數組和目標數,找到數組中有多少組不同的元素對,其和都等於給定的目標數,返回不同的元素對的對數
- 例如:
nums=[1,1,2,45,46,46],target = 47
- 返回
2
- 因爲:
1 + 46 = 47、2 + 45 = 47
思路
- 仍然是兩數之和問題,且不用返回下標,所以無需考慮數值和下標的問題
- 關鍵在於去重
- 雙指針和去重的一個首要操作都是排序
- 去重的一般手段就是利用
while
來找到不再重複的元素(具體見代碼和註釋)
代碼
public int twoSum6(int[] nums, int target) {
if (nums == null || nums.length == 0){
return 0;
}
//0. 首先排序,因爲此題不需要返回索引,所以無需考慮數值與下標的關係
//0.1 此處還有一個重要的目的在於爲後續去重做準備,(數組中去重問題一般都需要先排序)
Arrays.sort(nums);
int left = 0;
int right = nums.length - 1;
int cnt = 0;//計數
while (left < right){
int v = nums[left] + nums[right];
if (v == target){
//1. 如果找到一對數,那麼肯定計數+1,指針移動
cnt++;
left++;
right--;
//2. 去重,爲了避免一對同樣的數的和再組成target
while (left < right && nums[right] == nums[right+1]){
right--;
}
while (left < right && nums[left] == nums[left-1]){
left++;
}
}else if (v > target){
//3. 大了 要減小
right--;
}else {
//小了 要增大
left++;
}
}
return cnt;
}
5. LeetCode15. 3Sum
題意
- 給定數組,要求找到滿足
a +b + c = 0
的三元組,且無重複
思路
-
a + b + c = 0-> a +b = -c
-
遍歷數組,將每一個元素當作
-c = target
-
然後在數組剩餘元素中尋找無重複的兩數之和等於target的組合
-
那麼這時候問題就退化成了上面第4題
-
同時要注意對
target
也要去重- 去重的一個通用的思路是用
set
- 當然數組中可以有更簡單的方式
if (i > 0 && nums[i] == nums[i-1]){ continue; }
- 去重的一個通用的思路是用
代碼
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> results = new ArrayList<>();
List<Integer> item = new ArrayList<>();
if (nums == null || nums.length < 3){
return results;
}
//0. 先排序,無需返回index,所以直接排序即可
Arrays.sort(nums);
//1.遍歷nums,把每一個元素當作target,然後在數組剩下的部分尋找無重複的兩數之和等於target
// 問題退化成了two sum
int n = nums.length;
for (int i = 0; i < n; i++) {
//2.1 避免重複的target
if (i > 0 && nums[i] == nums[i-1]){
continue;
}
//2.2 a + b + c = 0 -> a + b = -c
int target = -nums[i];
//2.3 保證數組中剩下的元素至少有兩個
if (n - i - 1 >= 2) {
twoSum(i+1,target,nums,results);
}
}
return results;
}
private void twoSum(int startIndex, int target, int[] nums, List<List<Integer>> results) {
int left = startIndex;
int right = nums.length - 1;
while (left < right){
if (nums[left] + nums[right] == target){
List<Integer> item = new ArrayList<>();
item.add(-target);
item.add(nums[left]);
item.add(nums[right]);
results.add(item);
//指針移動
left++;
right--;
while (left < right && nums[left] == nums[left-1]){
left++;
}
while (left < right && nums[right] == nums[right +1]){
right--;
}
}else if (nums[left] + nums[right] < target){
//小了 要增大
left++;
}else {
//大了 要減小
right--;
}
}
}
}
6. LintCode-382. Triangle Count
題意
給定數組,要求找出能夠構成三角形的三元組的數量
思路
-
需要知道三條邊能夠構成一個三角形的充要條件
- 兩邊之和大於第三邊或兩邊之差小於第三邊(任意一個都是充要的)
-
那麼這道題就只需要遍歷數組元素,將每一個元素都當作
target
-
然後尋找兩數之和大於
target
的數量,所以仍然是一個兩數之和的問題 -
既然是兩數之和問題,所以仍然是先排序
-
一個比較關鍵的地方就是在於計數的問題
- 如果我們固定
nums[i]
,然後在[i+1,n-1]
這個區間內去找滿足這樣的一個不等式的數量,那麼就會出現求一個的組合數問題 - 所以爲了規避這個問題,我們從前面看,也就是固定
nums[i]
,然後在區間[0,i-1]
這個區間內去找滿足這個不等式的兩數數量 - 具體的實現細節查看代碼中的註釋
- 如果我們固定
代碼
public int triangleCount(int[] S) {
// write your code here
if ( S == null || S.length == 0){
return 0;
}
Arrays.sort(S);
int count = 0;
for (int i = 0; i < S.length; i++) {
//0. 以S[i] = target 在 區間[0,i-1]中尋找兩數之和大於 target
int left = 0;
int right = i - 1;
while (left < right){
if (S[left] + S[right] > S[i]){
//1. 因爲有序,所以 left 到 right區間內的任意兩數之和都大於 S[i]
//1.1 例如 3 4 5 6 7
// S[left] = 3 S[right] = 6 S[i] = 7
// 這裏本質上是把 S[right] 和S[i]都固定了,然後偶 left 到 right中的任一個數加上S[right]和S[i]
// 都滿足條件,所以數量就是從left 到 right-1 直接的數量,也就是right - left
count += right - left;
//2. 現在改變right,讓if中的左邊的和小一點,看能否找到更多的解
right -=1;
}else {
//3. 兩數之和<=target,所以需要增大
left++;
}
}
}
return count;
}
7. LintCode59. 3Sum Closest
題意
給定數組和目標數,要求在數組中找到一組三元組,其和離給定的目標數最小,返回這個和
思路
- 三數之和的題目,一定都是先固定一個數,然後降問題退化爲兩數之和
- 所以仍然是兩數之和的題目,所以先排序
- 這裏是求全局最接近,凡是求最值型問題,一定需要維護一個或多個全局變量,與臨時變量進行比較,然後更新
- 所以,這道題目只需要依次遍歷數組,固定
nums[i]
,然後從區間[i+1,n-1]
中尋找可能的離目標數最小的三個數 - 相關細節在代碼中
代碼
public int threeSumClosest(int[] numbers, int target) {
if (numbers == null || numbers.length == 0){
return 0;
}
Arrays.sort(numbers);
//0. 定義全局變量(凡是求最值型問題,都一定需要定義這樣的全局變量來和一些臨時變量進行比較更新)
int closestSum = Integer.MAX_VALUE;
int closestDiff = Integer.MAX_VALUE;
//1. 遍歷數組,先固定每個元素, -2是因爲從 i開始到最後至少需要三個元素 n -3 n-2 n-1
for (int i = 0; i < numbers.length - 2; i++) {
int e1 = numbers[i];
int left = i+1;
int right = numbers.length - 1;
while (left < right){
//2. 先求出當前的和以及和target的距離
int tempSum = e1 + numbers[left] + numbers[right];
int tempDiff = Math.abs(tempSum - target);
//3. 如果當前距離更短則更新
//3.1 這裏有一個細節是,進入了if之後沒有移動指針
// 這樣在下次的while循環中,一定會進入else分支,然後移動指針
if (tempDiff < closestDiff){
closestDiff = tempDiff;
closestSum = tempSum;
}else {
//4. 如果當前距離沒有更短,則需要縮短tempSum和target的距離
//4.1 具體怎麼縮短就需要看當前的具體情況了
if (tempSum < target){
left++;
}else {
right--;
}
}
}
}
return closestSum;
}
8. LeetCode18-4Sum
題意
給定數組和一個目標數,找到4個數之和等於該目標數的組合,返回這些組合
思路
- 遍歷數組,依次固定兩個數,然後把問題轉換爲兩數之和
- 注意對每一個數的去重
代碼
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> results = new ArrayList<>();
if (nums == null || nums.length == 0){
return results;
}
Arrays.sort(nums);
//1. i後面至少還需要3個數 n-4 n-3 n-2 n-1
for (int i = 0; i < nums.length - 3; i++) {
//去重
if (i > 0 && nums[i] == nums[i-1]){
continue;
}
int e1 = nums[i];
//1.1 j後面至少還要2個數 n-3 n-2 n-1
for (int j = i+1; j < nums.length - 2; j++) {
//去重 e1 和e2 是可以相等的
if (j != i + 1 && nums[j] == nums[j-1]){
continue;
}
int e2 = nums[j];
int left = j + 1;
int right = nums.length - 1;
while (left < right){
int sum = e1 + e2 + nums[left] + nums[right];
if (sum == target){
List<Integer> item = new ArrayList<>();
item.add(e1);
item.add(e2);
item.add(nums[left]);
item.add(nums[right]);
results.add(item);
left++;
right--;
//2. 去重
while (left < right && nums[left] == nums[left-1]){
left++;
}
while (left < right && nums[right] == nums[right+1]){
right--;
}
}else if (sum > target){
right--;
}else {
left++;
}
}
}
}
return results;
}