題意:詳見面試題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; } }
題意:詳見面試題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. 旋轉數組的最小數字
思路:使用二分的思想,每次將搜素範圍縮小。
假設[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位數
思路:最大的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. 調整數組順序使奇數位於偶數前面
思路:使用相似快排的思想,在數組先後分別置一個指針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. 順時針打印矩陣
思路:雙指針法。兩個指針分別位於矩陣的左上角和右下角。每次打印以兩個指針爲邊界的矩形範圍(打印是要考慮特殊狀況:只有一個元素、只有一行、只有一列...)。打印一圈以後縮小範圍,左上角指針向右下角移動,右下角矩陣向左上角移動。指針
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. 數組中出現次數超過一半的數字
思路:假設數組中超過一半的數字爲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個數
思路:使用大根堆。大根堆中結點個數維持在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. 數組中的逆序對
思路:歸併排序。將數組劃分兩等份,再將兩個子數組劃分...一直到子數組元素個數爲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
思路:二分查找。因爲數組是有序的,因此可使用二分查找,找到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中缺失的數字
思路:二分查找。數組總體上是遞增有序的,能夠採用二分查找的方式。比較數組下標和對應數字的關係。下標的值不會比數字大的狀況。對於中間的數字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. 數組中數字出現的次數
思路:位運算。先將數組中全部的數字進行異或運算,最後的結果即爲那兩個不相同的數字異或的結果,那麼這個結果其中有一位必然爲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
思路:位運算。若是可以肯定一個數字的二進制位上全部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的兩個數字
思路:雙指針。數組遞增排序,兩個指針指向的數字相加,大於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的連續正數序列
思路:滑動窗口。左右指針開始都在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. 滑動窗口的最大值
思路:單調棧。爲了實現方即可以使用雙端隊列。遍歷數組,當出現比尾部元素大的數字時,將尾部元素依次出隊,而後將當前元素入隊。保證中元素從頭至尾爲遞減的順序(從尾部插入)。
最大值爲頭部第一個元素。
在遍歷的過程當中,滑動窗口已經超過頭部元素的索引時,要將頭部元素彈出。
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. 撲克牌中的順子
思路:將數組排序。遍歷數組,記錄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. 構建乘積數組
思路:分別構建前綴和後綴乘積數組。而後兩部分的乘積就是最終結果。
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; } }