1. 程式人生 > 其它 >Leetcode 167:Two Sum II - Input array is sorted

Leetcode 167:Two Sum II - Input array is sorted

技術標籤:演算法指標資料結構leetcodejava

Leetcode 167:Two Sum II - Input array is sorted

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

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2.

Note:

  • Your returned answers (both index1 and index2) are not zero-based.
  • You may assume that each input would have exactly one solution and you may not use the same element twice.

說人話:

一個有序陣列中找 2 個元素和為 target,返回它們的索引(索引從1開始)。

幾個要點:

  • 陣列本身有序
  • 索引從1開始計算
  • 一個元素不能使用2次
  • 本題保證有解、且是唯一解

[法1] 暴力法

思路

暴力解法思路非常清晰,就是雙層遍歷陣列。

程式碼
class Solution {
    public int[] twoSum(int[] numbers, int target) {
      	int[] result = new int[2];
				for(int i=0;i<numbers.length-1;i++){
          	for(int j=i+1; j<numbers.length;j++){
              if((numbers[i] + numbers[j]) == target){
                    result[0] = i+1;
                    result[
1] = j+1; return result; } } } return result; } }
提交結果
image-20210122102855320
程式碼分析
  • 時間複雜度:雙層遍歷,很明顯是 O(n2)
  • 空間複雜度:O(1)
改進思路

很明顯這裡時間複雜度 O(n2) 是比較大的,我們改進的方向應該就是想辦法儘可能少遍歷陣列。

暴力演算法中雙層遍歷並沒有利用到題目給的一個有用資訊:陣列本身有序,所以我們可以從這個突破口尋求改進的方法。

[法2] 二分查詢

思路

因為陣列本身有序,那麼我們就想到了二分查詢法。所以我們在確定了 nums[i] 後要找 nums[j] 的時候,這個時候找 nums[j] 就可以用二分查詢,這樣時間複雜度就優化為 O(nlogn) 了。

程式碼
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        //結果
        int[] result = new int[2];
        
        //遍歷,首先確定 numbers[i]
        for(int i=0;i<numbers.length-1;i++){
            //二分查詢 numbers[j]
            int jIndex = binarySearch(numbers,i+1,numbers.length-1, target-numbers[i]);
            if( jIndex != -1){
                result[0] = i+1;
                result[1] = jIndex+1;
            }
        }
        return result;
    }

    //二分查詢法
    public int binarySearch(int[] nums, int start, int end, int target){
        //從 [start...end] 中查詢
        int l = start;
        int r = end;
        while( l<=r ){
            int mid = l + (r-l)/2;
            if(nums[mid] == target){
                return mid;
            }else if(nums[mid] < target){
                l = mid+1;
            }else if(nums[mid] > target){
                r = mid-1;
            }
        }
        return -1;
    }
}
提交結果
image-20210122110740685
程式碼分析
  • 時間複雜度:因為第一層遍歷是迴圈遍歷,第二層遍歷是二分查詢,故時間複雜度是 O(nlogn)
  • 空間複雜度:O(1)
改進思路

這次我們用上了陣列的有序性這個有利條件,大大降低了時間複雜度。但是有沒有可能只遍歷一次陣列就解決問題呢?

[法3] 對撞指標

思路

因為陣列本身是有序的,所以我們可以利用這個有序性,建立兩個指標,一個在頭一個在尾,兩個不斷向中間夾,直到找到兩個和為 target 的元素。

整體思路:

  • 建立頭指標 head 和 尾指標 tail
  • 計算 numbers[head] + numbers[tail] 的值
  • 如果 == target,完成
  • 如果 < target,則說明 numbers[head] 不夠大,就 head++
  • 如果 > target,則說明 numbers[tail] 太大,就 tail–
  • 不斷向中間夾,直到 numbers[head] + numbers[tail] == target
  • 如果 head = tail,這說明找不到
程式碼
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        //頭指標
      	int head = 0;
        //尾指標
        int tail = numbers.length-1;
        //結果
        int[] result = new int[2];
        //兩邊夾
        while(head < tail){
            int sum = numbers[head] + numbers[tail];
            //== target,完成
            if(sum == target){
                result[0] = head+1;
                result[1] = tail+1;
                break;
            }
            // < target,說明 numbers[head] 太小,head++
            else if(sum < target){
                head++;
            }
            // > target,說明 numbers[tail] 太大,tail++
            else if(sum > target){
                tail--;
            }
        }
        return result;
    }
}
提交結果
image-20210122105654402
程式碼分析
  • 時間複雜度:O(n)
  • 空間複雜度:O(1)