劍指 offer——數組篇

3. 數組中重複的數字

題意:詳見面試題03. 數組中重複的數字
思路1:使用Hash表。遍歷整個數組,在訪問到某個數字時,先在Hash表中查詢是否已包含此數字,若是包含,返回此數字便可;若是不包含,則將數字加到Hash表中,繼續遍歷下一個數字。java

class Solution {
    public int findRepeatNumber(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for (int i : set) {
            if (set.contains(i)) {
                return i;
            } else {
                set.add(i);
            }
        }
        return -1;
    }
}

思路2:因爲數組的大小爲n,數字範圍爲[0,n-1]。咱們能夠在遍歷中將數字換到數組中下標對應的位置,即將0換到數組index爲0的位置。若是對應的位置已經有數字了,表示當前遍歷的數字是重複的,那麼返回當前的數字。面試

class Solution {
    public int findRepeatNumber(int[] nums) {
        for (int i = 0; i < nums.length; i ++) {
            if (nums[i] != i && nums[i] == nums[nums[i]]) {
                return nums[i];
            }
            while (nums[i] != i && nums[i] != nums[nums[i]]) {
                swap(nums, i, nums[i]);
            }
        }
        return -1;
    }

    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

4. 二維數組中的查找

題意:詳見面試題04. 二維數組中的查找
思路:(1)從左到右、從上到下都是遞增關係
(2)搜索過程若是是從左上向右下(或從右下向左上),那麼在當前值比目標值小時,將要搜索兩個方向(向右和向下的元素都比當前值要大),因此此法將可能遍歷整個二維數組,時間複雜度O(n*m)
(3)能夠從右上角or左下角開始搜索,每次與target比較後,只有一個方向能夠搜索,時間複雜度O(m+n)數組

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        int h = matrix.length;
        if (h == 0) {
            return false;
        }
        int w = matrix[0].length;
        if (w == 0) {
            return false;
        }
        int i = 0, j = w - 1;
        while (i < h && j >= 0) {
            if (matrix[i][j] == target) {
                return true;
            } else if (matrix[i][j] > target) {
                j --;
            } else {
                i ++;
            }
        }
        return false;
    }
}

11. 旋轉數組的最小數字

題意:面試題11. 旋轉數組的最小數字
思路:使用二分的思想,每次將搜素範圍縮小。
假設[left, right]爲當前的搜索範圍。取中間的位置mid,與right位置的數字比較:
1)若是mid位置的數字 > right位置的數字,那麼最小數字必定在搜索範圍的後半段,因此須要將left置爲mid+1;
2)若是mid位置的數字 < right位置的數字,那麼最小數字必定在搜索範圍的前半段(包括mid位置),因此須要將right置爲mid;
3)若是mid位置的數字 = right位置的數字,此時沒法判斷最小數字的位置,因此只能逐步縮小搜索範圍,將right--。函數

class Solution {
    public int minArray(int[] numbers) {
        int left = 0;
        int right = numbers.length - 1;
        int mid;
        while (left < right) {
            mid = left + ((right - left) >> 1);
            if (numbers[mid] > numbers[right]) {
                left = mid + 1;
            } else if (numbers[mid] < numbers[right]){
                right = mid;
            } else {
                right --;
            }
        }
        return numbers[left];
    }
}

17. 打印從1到最大的n位數

題意:面試題17. 打印從1到最大的n位數
思路:最大的n位數加上1必定是1+n個0,因此題目給出了n,就能計算出10的n次方的值,並以此值做爲打印的上邊界。post

class Solution {
    public int[] printNumbers(int n) {
        int ten = 1;
        int i = 1;
        while (i <= n) {
            ten *= 10;
            i ++;
        }
        int[] res = new int[ten - 1];
        for (i = 0; i < res.length; i ++) {
            res[i] = i + 1;
        }
        return res;
    }
}

21. 調整數組順序使奇數位於偶數前面

題意:面試題21. 調整數組順序使奇數位於偶數前面
思路:使用相似快排的思想,在數組先後分別置一個指針left、right,left從前向後尋找偶數,right從後向前尋找奇數,而後交換兩個位置的數字並移動兩指針,直到兩指針碰到一塊兒結束。ui

class Solution {
    public int[] exchange(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            while (left < right && (nums[left] & 1) == 1) {
                left ++;
            }
            while (left < right && (nums[right] & 1) == 0) {
                right --;
            }
            swap(nums, left++, right--);
        }
        return nums;
    }

    private void swap(int[] nums, int left, int right) {
        int tmp = nums[left];
        nums[left] = nums[right];
        nums[right] = tmp;
    }
}

29. 順時針打印矩陣

題意:面試題29. 順時針打印矩陣
思路:雙指針法。兩個指針分別位於矩陣的左上角和右下角。每次打印以兩個指針爲邊界的矩形範圍(打印是要考慮特殊狀況:只有一個元素、只有一行、只有一列...)。打印一圈以後縮小範圍,左上角指針向右下角移動,右下角矩陣向左上角移動。指針

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0) {
            return new int[0];
        }
        int width = matrix[0].length;
        int height = matrix.length;
        int[] res = new int[width * height];
        int leftX = 0, leftY = 0;
        int rightX = height - 1, rightY = width - 1;
        int start = 0;
        while (start < res.length) {
            start = print(matrix, leftX, leftY, rightX, rightY, res, start);
            leftX ++;
            leftY ++;
            rightX --;
            rightY --;
        }
        return res;
    }

    private int print(int[][] matrix, int leftX, int leftY, int rightX, int rightY, int[] res, int start) {
        if (leftX == rightX && leftY == rightY) {
            res[start ++] = matrix[leftX][leftY];
        } else if (leftX == rightX) {
            for (int i = leftY; i <= rightY; i ++) {
                res[start ++] = matrix[leftX][i];
            }
        } else if (leftY == rightY) {
            for (int i = leftX; i <= rightX; i ++) {
                res[start ++] = matrix[i][leftY];
            }
        } else {
            int i = leftX, j = leftY;
            while (j < rightY) {
                res[start ++] = matrix[i][j ++];
            }
            while (i < rightX) {
                res[start ++] = matrix[i ++][j];
            }
            while (j > leftY) {
                res[start ++] = matrix[i][j --];
            }
            while (i > leftX) {
                res[start ++] = matrix[i --][j];
            }
        }
        return start;
    }
}

print函數的另一種寫法:code

private int print(int[][] matrix, int leftX, int leftY, int rightX, int rightY, int[] res, int start) {
        for (int i = leftY; i <= rightY; i ++) {
            res[start ++] = matrix[leftX][i];
        }
        for (int j = leftX + 1; j <= rightX; j ++) {
            res[start ++] = matrix[j][rightY];
        }
        if (leftX < rightX && leftY < rightY) {
            for (int i = rightY - 1; i >= leftY; i --) {
                res[start ++] = matrix[rightX][i];
            }
            for (int j = rightX - 1; j > leftX; j --) {
                res[start ++] = matrix[j][leftY];
            }
        }
        return start;
    }

39. 數組中出現次數超過一次的數字

題意:面試題39. 數組中出現次數超過一半的數字
思路:假設數組中超過一半的數字爲x,若是同時刪除一個x和另外一個非x,那麼最後數組中剩下的仍然是x.
遍歷數組,使用一個候選值candidate和一個計數count記錄當前比較多的數字,若是遍歷到的數字等於candidate,那麼count加一。
若是遍歷到的數字不爲candidate,那麼count減一,當count減到0的時候,將candidate賦值爲此時遍歷到的數字。
最後candidate記錄的數字就是出現次數超過一半的數字。排序

class Solution {
    public int majorityElement(int[] nums) {
        int candidate = nums[0];
        int count = 1;
        for (int i = 1; i < nums.length; i ++) {
            if (nums[i] == candidate) {
                count ++;
            } else {
                if (count == 0) {
                    candidate = nums[i];
                } else {
                    count --;
                }
            }
        }
        return candidate;
    }
}

40. 最小的k個數

題意:面試題40. 最小的k個數
思路:使用大根堆。大根堆中結點個數維持在k個。遍歷的過程當中,若是當前元素比根結點的值小,那麼將當前元素插入大根堆中,並將堆頂元素彈出。遍歷完以後大根堆中保存的就是最小的k個數。
Java中大根堆可使用優先隊列來實現。索引

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>((i1, i2) -> {
            return i2 - i1;
        });
        for (int i : arr) {
            if (queue.size() >= k) {
                if (!queue.isEmpty() && i < queue.peek()) {
                    queue.poll();
                    queue.add(i);
                }
            } else {
                queue.add(i);
            }
        }
        int[] res = new int[queue.size()];
        for (int i = 0; i < res.length; i ++) {
            res[i] = queue.poll();
        }
        return res;
    }
}

51. 數組中的逆序對

題意:面試題51. 數組中的逆序對
思路:歸併排序。將數組劃分兩等份,再將兩個子數組劃分...一直到子數組元素個數爲1,而後兩兩進行排序,在排序(合併)的過程當中計算逆序對。

class Solution {
    public int reversePairs(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        return reversePairs(nums, 0, nums.length - 1);
    }

    private int reversePairs(int[] nums, int start, int end) {
        if (start == end) {
            return 0;
        }
        int mid = (start + end) >> 1;
        int left = reversePairs(nums, start, mid);
        int right = reversePairs(nums, mid + 1, end);
        int i = start, j = mid + 1;
        int[] help = new int[end - start + 1];
        int pairs = left + right;
        int k = 0;
        while (i <= mid && j <= end) {
            if (nums[i] > nums[j]) {
                pairs += (mid - i + 1);
                help[k++] = nums[j++];
            } else {
                help[k++] = nums[i++];
            }
        }
        int tmp = (i > mid) ? j : i;
        int tmpEnd = (i > mid) ? end : mid;
        while (tmp <= tmpEnd) {
            help[k++] = nums[tmp++];
        }
        for (int t = 0; t < help.length; t ++) {
            nums[start + t] = help[t];
        }
        return pairs;
    }
}

53-I. 在排序數組中查找數字 I

題意:面試題53 - I. 在排序數組中查找數字 I
思路:二分查找。因爲數組是有序的,因此可使用二分查找,找到target的最左邊的索引left和最右邊的索引right,二者差值就是要求的次數。

class Solution {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int right = maxIndex(nums, target);
        if (right < 0) {
            return 0;
        }
        int left = minIndex(nums, target);
        return right - left + 1;
    }

    private int minIndex(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;
        int mid;
        while (low < high) {
            mid = low + ((high - low) >> 1);
            if (nums[mid] >= target) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return nums[low] == target ? low : -1;
    }

    private int maxIndex(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;
        int mid;
        while (low < high) {
            mid = low + (high - low + 1) / 2;
            if (nums[mid] <= target) {
                low = mid;
            } else {
                high = mid - 1;
            }
        }
        return nums[high] == target ? high : -1;
    }
}

53-II. 0~n-1中缺失的數字

題意:面試題53 - II. 0~n-1中缺失的數字
思路:二分查找。數組總體上是遞增有序的,能夠採用二分查找的方式。比較數組下標和對應數字的關係。下標的值不會比數字大的狀況。對於中間的數字mid,若是和下標一致,那麼缺乏的數字必定在mid以後。不然下標值小於對應的數字,那麼缺失的值必定在mid前。

class Solution {
    public int missingNumber(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        int mid;
        while (low <= high) {
            mid = low + ((high - low) >> 1);
            if (nums[mid] == mid) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return nums[low] == low ? nums[low] + 1: nums[low] -1;
    }
}

56-I. 數組中數字出現的次數

題意:面試題56 - I. 數組中數字出現的次數
思路:位運算。先將數組中全部的數字進行異或運算,最後的結果即爲那兩個不相同的數字異或的結果,那麼這個結果其中有一位必然爲1。按照該位是否爲1將數組分爲兩組再進行異或運算,那麼最後的結果就是這兩個數字。

class Solution {
    public int[] singleNumbers(int[] nums) {
        int sum = 0;
        for (int i : nums) {
            sum ^= i;
        }
        int oneBit = sum & (-sum);
        int num1 = 0;
        int num2 = 0;
        for (int i : nums) {
            if ((i & oneBit) == 0) {
                num1 ^= i;
            } else {
                num2 ^= i;
            }
        }
        return new int[]{num1, num2};
    }
}

56-II. 數組中數字出現的次數 II

題意:面試題56 - II. 數組中數字出現的次數 II
思路:位運算。若是可以肯定一個數字的二進制位上全部1的位置,就能夠惟一肯定這個數字。因此只須要對數組中每一個數字的每一個二進制位上的1進行計數,最後判斷哪一位上1的個數不是3的倍數,那麼出現一次的數字該位上就爲1。

class Solution {
    public int singleNumber(int[] nums) {
        int[] bits = new int[32];
        for (int i : nums) {
            for (int j = 31; j >= 0; j --) {
                if (((i >> j) & 1) == 1) {
                    bits[j] ++;
                }
            }
        }
        int res = 0;
        for (int i = 0; i < 32; i ++) {
            if (bits[i] % 3 != 0) {
                res |= (1 << i);
            }
        }
        return res;
    }
}

57. 和爲s的兩個數字

題意:面試題57. 和爲s的兩個數字
思路:雙指針。數組遞增排序,兩個指針指向的數字相加,大於target則右指針左移,小於target則左指針右移。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            if (nums[left] + nums[right] == target) {
                return new int[]{nums[left], nums[right]};
            } else if (nums[left] + nums[right] > target) {
                right--;
            } else {
                left++;
            }
        }
        return new int[0];
    }
}

57-II. 和爲s的連續正數序列

題意:面試題57 - II. 和爲s的連續正數序列
思路:滑動窗口。左右指針開始都在0位置。右指針向後移動,一直到大於或等於target時,左指針向右移動一位。重複上述步驟,直到左指針位置已經超過target的一半。

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> list = new ArrayList<>();
        int left = 0;
        int right = 0;
        int sum = 0;
        LinkedList<Integer> queue = new LinkedList<>();
        int[] tmp;
        while (left < target/2 + 1) {
            if (sum < target) {
                right ++;
                queue.add(right);
                sum += right;
            } else if (sum > target) {
                queue.removeFirst();
                left ++;
                sum -= left;
            } else {
                tmp = queue.stream().mapToInt(i -> i).toArray();
                list.add(tmp);
                right ++;
                queue.add(right);
                sum += right;
            }
        }
        int[][] out = new int[list.size()][];
        return list.toArray(out);
    }
}

59-I. 滑動窗口的最大值

題意:面試題59 - I. 滑動窗口的最大值
思路:單調棧。爲了實現方即可以使用雙端隊列。遍歷數組,當出現比尾部元素大的數字時,將尾部元素依次出隊,而後將當前元素入隊。保證中元素從頭至尾爲遞減的順序(從尾部插入)。
最大值爲頭部第一個元素。
在遍歷的過程當中,滑動窗口已經超過頭部元素的索引時,要將頭部元素彈出。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return new int[0];
        }
        Deque<Integer> queue = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];
        for (int i = 0; i < nums.length; i ++) {
            while (!queue.isEmpty() && nums[queue.getLast()] < nums[i]) {
                queue.removeLast();
            }
            queue.add(i);
            if (i >= k - 1) {
                if (i > k - 1) {
                    while (!queue.isEmpty() && i - k >= queue.getFirst()) {
                        queue.removeFirst();
                    }
                }
                res[i - k + 1] = nums[queue.getFirst()];
            }
        }
        return res;
    }
}

61. 撲克牌中的順子

題意:面試題61. 撲克牌中的順子
思路:將數組排序。遍歷數組,記錄0(大小王)的個數joker,而且從第一個不爲0的數字開始向後遍歷,若前一個元素pre和後一個元素post相差大於1,那麼就要將joker個數減一,表示使用一張大小王代替了pre和post之間的一個數字。若joker數量減小到負數了,pre和post仍然相差大於1,那麼沒法使這兩張牌連續,直接返回false。

class Solution {
    public boolean isStraight(int[] nums) {
        Arrays.sort(nums);
        int joker = 0;
        int index = 0;
        for (; index < nums.length; index ++) {
            if (nums[index] == 0) {
                joker ++;
            } else {
                break;
            }
        }
        for (int i = index + 1; i < nums.length; i ++) {
            if (nums[i] != nums[i - 1] + 1) {
                while (joker >= 0 && nums[i] != nums[i-1] + 1) {
                    joker --;
                    nums[i - 1] ++;
                }
                if (joker < 0) {
                    return false;
                }
            }
        }
        return true;
    }
}

66. 構建乘積數組

題意:面試題66. 構建乘積數組
思路:分別構建前綴和後綴乘積數組。而後兩部分的乘積就是最終結果。

class Solution {
    public int[] constructArr(int[] a) {
        if (a == null || a.length == 0) {
            return new int[0];
        }
        int[] pre = new int[a.length];
        pre[0] = 1;
        int[] post = new int[a.length];
        post[a.length - 1] = 1;
        for (int i = 1; i < a.length; i ++) {
            pre[i] = pre[i - 1] * a[i - 1];
        }
        for (int i = a.length - 2; i >= 0; i --) {
            post[i] = post[i + 1] * a[i + 1];
        }
        int[] res = new int[a.length];
        for (int i = 0; i < a.length; i ++) {
            res[i] = pre[i] * post[i];
        }
        return res;
    }
}
相關文章
相關標籤/搜索