面試 19:輸出數組中出現次數超過一半的數字(劍指 Offer 26 題)
上一篇推文給大家留下的習題來自於《劍指 Offer》第 29 題:數組中超過一半的數字,不知道各位去思考了么?
面試題:數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字並輸出。比如 {1,2,3,2,2,2,1} 中 2 的次數是 4,數組長度為 7,所以輸出 2。要求不能修改輸入的數組。
准備測試用例
這道題能思考到的測試用例比較簡單。
- 輸入符合條件的數組,查看打印是否滿足情況;
- 輸入不符合條件的數組,查看打印;
- 輸入只有一個元素的數組,查看打印;
- 輸入無效數組,查看打印;
思考程序邏輯
第二步便是我們的思考程序邏輯了,題目要求查找出現次數超過一半的數字。比較容易想到的思路是直接對數組排序,那中間那個值就是我們想要的值,但這樣的想法明顯排序后會對輸入的數組順序有影響,所以我們可能需要換一種思路。
再看一遍題干,我們不難思考到,我們是否可以對每個數字進行計數,最后返回計數次數最多的值。存儲次數采用 map 做映射處理。
public class Test19 { private static int moreThanHalfNums(int[] nums) { if (nums == null || nums.length == 0) throw new RuntimeException("the length of array must be large than 0"); int len = nums.length; Map<Integer, Integer> map = new HashMap<>(); for (int num : nums) { if (map.containsKey(num)) map.put(num, map.get(num) + 1); else map.put(num, 1); } int times = len / 2; // 查找 map 中 value 最大的值 for (Entry<Integer, Integer> entry : map.entrySet()) { if (entry.getValue() > times) return entry.getKey(); } throw new RuntimeException("invalid input!"); } public static void main(String[] args) { int[] nums1 = {1, 2, 3, 2, 2, 4, 2, 2, 5}; System.out.println(moreThanHalfNums(nums1)); int[] nums2 = {1}; System.out.println(moreThanHalfNums(nums2)); int[] nums3 = {2, 1, 2, 1, 2, 2, 3, 2, 1}; System.out.println(moreThanHalfNums(nums3)); int[] nums4 = {1, 2, 3, 4, 5}; System.out.println(moreThanHalfNums(nums4)); } }
寫畢后進行測試用例的驗證,無不例外,目前都通過,於是我們把這樣的代碼解法遞交給面試官。
面試官看了這樣的算法,表示他更期待的是不使用任何輔存空間的算法。於是我們得換個角度思考。
數組中有一個數字出現的次數超過數組長度的一半,也就是說它出現的次數比其他所有數字出現次數的和還要多。因此我們可以考慮在遍歷數組的時候保存兩個值: 一個是數組中的一個數字, 一個是次數。當我們遍歷到下一個數字的時候,如果下一個數字和我們之前保存的數字相同,則次數加 1 ;如果下一個數字和我們之前保存的數不同,則次數減 1。如果次數為 0,我們需要保存下一個數字,並把次數設為 1 。由於我們要找的數字出現的次數比其他所有數字出現的次數之和還要多,那么要找的數字肯定是最后一次把次數設為 1 時對應的數字。
我們來看這樣的思路用代碼怎么實現。
public class Test19 { private static int moreThanHalfNums(int[] nums) { if (nums == null || nums.length == 0) throw new RuntimeException("the length of array must be large than 0"); int result = nums[0]; int times = 1; int len = nums.length; for (int i = 1; i < len; i++) { if (times == 0) { result = nums[i]; times = 1; } else if (result == nums[i]) times++; else times--; } times = 0; for (int num : nums) { if (num == result) times++; } if (times > len / 2) return result; throw new RuntimeException("invalid input!"); } public static void main(String[] args) { int[] nums1 = {1, 2, 3, 2, 2, 4, 2, 2, 5}; System.out.println(moreThanHalfNums(nums1)); int[] nums2 = {1}; System.out.println(moreThanHalfNums(nums2)); int[] nums3 = {2, 1, 2, 1, 2, 2, 3, 2, 1}; System.out.println(moreThanHalfNums(nums3)); int[] nums4 = {1, 2, 3, 4, 5}; System.out.println(moreThanHalfNums(nums4)); } }
寫畢后,驗證測試用例,同樣全部通過。
本題最后的思路,希望大家刻意去思考和記憶一下,因為也許變一下題意,這樣的想法還可以用到。
課后習題
我們下一次推文的題目來源於《劍指 Offer》第 31 題:計算連續子數組的最大和。
面試題:輸入一個整型數組,數組中有正數也有負數。數組中一個或多個整數形成一個子數組,求所有子數組的和的最大值,要求時間復雜度為 O(n)。
比如輸入 {1, -2, 3, 10, -4, 7, 2, -5},能產生子數組最大和的子數組為 {3,10,-4,7,2},最大和為 18。
