LeetCode基礎算法-數組
算法 LeetCode 數組相關
1. 從排序數組中刪除重復項
描述:
給定一個排序數組,你需要在原地刪除重復出現的元素,使得每個元素只出現一次,返回移除后數組的新長度。
不要使用額外的數組空間,你必須在原地修改輸入數組并在使用 O(1) 額外空間的條件下完成。
解答思路:
- 雙指針法,index指針指向當前已無重復數字數組的最后一位;i指針遍歷nums數組。
- i與index處值相同時,i指針繼續向后遍歷。
- i與index處值相同時,將i處值復制給++index處。
- 無重復數組的長度為index+1。
代碼:
public int removeDuplicates(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
// index從索引0開始
int index = 0;
// i從索引1開始遍歷
for (int i = 1; i < nums.length; i++) {
// 不相同則進行擴展,index永遠指向符合條件數組的最后一個索引
if (nums[i] != nums[index]) {
nums[++index] = nums[i];
}
}
return index + 1;
}
2. 買賣股票的最佳時機
描述:
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨后,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之后再將它們賣出。
因為這樣屬于同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
解答思路(貪心算法):
- minPrice指向當前的最低價(假設為買入),sum記錄當前盈利的最大值。
- minPrice初始化為數組第一個值。
- 當前值<minPrice,此時賣出會賠錢,因此不能賣出,將當前值賦值給minPrice。
- 當前值>minPrice,此時賣出可以賺錢,因此賣出,將當前值賦值給minPrice。
代碼實現:
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0){
return 0;
}
int sum = 0;
int minPrice = prices[0];
for(int i = 1;i<prices.length;i++){
if(prices[i]>minPrice){
// 此時賣出。
sum += prices[i] - minPrice;
minPrice = prices[i];
}else{
// 以當前價格買入
minPrice = prices[i];
}
}
return sum;
}
3. 旋轉數組
描述:
給定一個數組,將數組中的元素向右移動 k 個位置,其中 k 是非負數。
示例 1:
輸入: [1,2,3,4,5,6,7] 和 k = 3
輸出: [5,6,7,1,2,3,4]
解釋:
向右旋轉 1 步: [7,1,2,3,4,5,6]
向右旋轉 2 步: [6,7,1,2,3,4,5]
向右旋轉 3 步: [5,6,7,1,2,3,4]
解題思路:
- 如果K為數組長度的整倍數,那么無需做任何操作。
- k = k%length
- 循環右移數組,將數組的最后一個值賦值給數組第一個值。
public void rotate(int[] nums, int k) {
if (null == nums || nums.length == 0) {
return;
}
if (k % nums.length == 0) {
return;
}
k = k % nums.length;
int last;
for (int i = 0; i < k; i++) {
last = nums[nums.length - 1];
for (int j = nums.length - 1; j > 0; j--) {
nums[j] = nums[j - 1];
}
nums[0] = last;
}
}
4. 只出現一次的數字
給定一個非空整數數組,除了某個元素只出現一次以外,其余每個元素均出現兩次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性時間復雜度。 你可以不使用額外空間來實現嗎?
解題思路:
- 使用數學上的異或的特點,兩個相同的數字異或等于0;
- 11=0,11^2=2
代碼:
public int singleNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return -1;
}
int sigleNum = nums[0];
for (int i = 1; i < nums.length; i++) {
sigleNum ^= nums[i];
}
return sigleNum;
}
5. 兩個數組的交集
描述:
給定兩個數組,編寫一個函數來計算它們的交集。
解題思路:
- 先排序,然后使用三個指針:i,j,z來遍歷保存數據。
// 兩個數組的交集
public int[] intersect(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null) {
return new int[0];
}
Arrays.sort(nums1);
Arrays.sort(nums2);
int[] result;
if (nums1.length > nums2.length) {
result = new int[nums2.length];
} else {
result = new int[nums1.length];
}
int i = 0, j = 0, z = 0;
while (i < nums1.length && j < nums2.length) {
if (nums1[i] == nums2[j]) {
result[z++] = nums1[i];
i++;
j++;
} else if (nums1[i] > nums2[j]) {
j++;
} else {
i++;
}
}
int realResult[] = new int[z];
for (int index = 0; index < z; index++) {
realResult[index] = result[index];
}
return realResult;
}
6. 加1
給定一個由整數組成的非空數組所表示的非負整數,在該數的基礎上加一。
最高位數字存放在數組的首位, 數組中每個元素只存儲一個數字。
你可以假設除了整數 0 之外,這個整數不會以零開頭。
解題思路:
- 從數組尾部遍歷,職要當前位不為9,那么當前位+1,循環結束。
- 如果當前位為9,則當前位設置為0,繼續向前遍歷。
public int[] plusOne(int[] digits) {
if (digits == null || digits.length == 0) {
return new int[0];
}
for (int i = digits.length - 1; i >= 0; i--) {
if (digits[i] == 9) {
digits[i] = 0;
} else {
digits[i] += 1;
return digits;
}
}
int[] newDigits = new int[digits.length + 1];
for (int i = 0; i < digits.length; i++) {
newDigits[i] = digits[i];
}
newDigits[0] = 1;
return newDigits;
}
7. 移動零
給定一個數組 nums,編寫一個函數將所有 0 移動到數組的末尾,同時保持非零元素的相對順序。
示例:
輸入: [0,1,0,3,12]
輸出: [1,3,12,0,0]
說明:
必須在原數組上操作,不能拷貝額外的數組。
盡量減少操作次數。
解題思路:
- 使用雙指針循環遍歷。
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
int index = 0, start = 0;
while (start < nums.length) {
if (nums[start] != 0) {
nums[index++] = nums[start];
}
start++;
}
while (index < nums.length) {
nums[index] = 0;
index++;
}
}
8. 兩數之和
給定一個整數數組和一個目標值,找出數組中和為目標值的兩個數。
你可以假設每個輸入只對應一種答案,且同樣的元素不能被重復利用。
解題思路:
- 使用Map,遍歷放入時檢查map中是否已經存在余數值,存在立即返回;不存在繼續遍歷。
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
if (map.get(target - nums[i]) != i) {
return new int[]{map.get(target - nums[i]), i};
}
}
map.put(nums[i], i);
}
return new int[]{-1, -1};
}
9. 有效的數獨
判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。
數字 1-9 在每一行只能出現一次。
數字 1-9 在每一列只能出現一次。
數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。
解題思路:
使用三個數組來記錄是否已經存在該值的信息。
rowFlag[9][9]:rowFlag[i][c],第i行是否出現了值c。
colFlag[9][9]:rowFlag[c][j],c出現在第j列。
cellFalg[9][9]:cell[3(i/3)+j/3],第3(i/3)+j/3個單元中出現了c。
public boolean isValidSudoku(char[][] board) {
boolean rowFlag[][] = new boolean[9][9];
boolean colFlag[][] = new boolean[9][9];
boolean cellFlag[][] = new boolean[9][9];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; i++) {
if (board[i][j] >= '1' && board[i][j] <= '9'){
int c = board[i][j] - '1';
if (rowFlag[i][c] || colFlag[j][c] || cellFlag[3 * (i / 3) + j / 3][c]) {
return false;
}
rowFlag[i][c] = true;
colFlag[j][c] = true;
cellFlag[3 * (i / 3) + j / 3][c] = true;
}
}
}
return true;
}
10. 旋轉數組
給定一個 n × n 的二維矩陣表示一個圖像。
將圖像順時針旋轉 90 度。
說明:
你必須在原地旋轉圖像,這意味著你需要直接修改輸入的二維矩陣。請不要使用另一個矩陣來旋轉圖像。
給定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋轉輸入矩陣,使其變為:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
public void rotate(int[][] matrix) {
// 首先沿著對角線旋轉
for (int i = 0; i < matrix.length; i++) {
for (int j = i + 1; j < matrix.length; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 再沿著數組垂直中線進行旋轉
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length / 2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[i][matrix.length - 1 - j];
matrix[i][matrix.length - 1 - j] = temp;
}
}
}
11. 三數之和
給定一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重復的三元組。
注意:答案中不可以包含重復的三元組。
例如, 給定數組 nums = [-1, 0, 1, 2, -1, -4],
滿足要求的三元組集合為:
[
[-1, 0, 1],
[-1, -1, 2]
]
解題思路:
- 首先對數組進行排序,逐個遍歷數組,以index上的數據nums[index]為例分析:
- 如果nums[index]>0,結束循環,因為此后的數組值必大于0。
- 如果nums[index]<=0,定義target = 0-nums[index],兩數之和等于target的過程。
- 我們使用雙指針來解決兩數之和為target的求解過程。
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> re = new ArrayList<>();
if (nums == null || nums.length < 3) {
return re;
}
// 1. 首先排序
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 選取第一個值
if (nums[i] > 0) break;
// 去除重復
if (i >= 1) if (nums[i] == nums[i - 1]) continue;
int target = 0 - nums[i];
int low = i + 1;
int high = nums.length - 1;
while (low < high) {
if (nums[low] + nums[high] == target) {
List<Integer> list = new ArrayList();
list.add(nums[i]);
list.add(nums[low]);
list.add(nums[high]);
re.add(list);
// 為了去重復。
while (low + 1 < high && nums[low] == nums[low + 1]) {
low++;
}
// 為了去重復。
while (high - 1 > low && nums[high] == nums[high - 1]) {
high--;
}
low++;
high--;
} else if (nums[low] + nums[high] < target) {
low++;
} else {
high--;
}
}
}
return re;
}
12. 矩陣置零
給定一個 m x n 的矩陣,如果一個元素為 0,則將其所在行和列的所有元素都設為 0。請使用原地算法。
示例 1:
輸入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
輸出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
輸入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
輸出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
進階:
一個直接的解決方案是使用 O(mn) 的額外空間,但這并不是一個好的解決方案。
一個簡單的改進方案是使用 O(m + n) 的額外空間,但這仍然不是最好的解決方案。
你能想出一個常數空間的解決方案嗎?
解題思路:
- 我們直接使用常數空間來解決問題。
- 首先我們借助s0行和0列來存儲哪些行和類稍后需要置為0。
- 其次我們定義兩個遍歷來分別記錄0行0列原來是否出現過0。
public void setZeroes(int[][] matrix) {
int rows = matrix.length;
int cols = matrix[0].length;
boolean firstRowIsZero = false;
boolean firstColIsZero = false;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (matrix[i][j] == 0) {
if (i != 0 && j != 0) {
matrix[i][0] = 0;
matrix[0][j] = 0;
} else {
firstColIsZero = j == 0 ? true : firstColIsZero;
firstRowIsZero = i == 0 ? true : firstRowIsZero;
}
}
}
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < cols; j++) {
if (matrix[0][j] == 0 || matrix[i][0] == 0) {
matrix[i][j] = 0;
}
}
}
//第一列含0
if(firstColIsZero){
for(int i=0;i<matrix.length;i++){
matrix[i][0] = 0;
}
}
//第一行含0
if(firstRowIsZero){
for(int j=0;j<matrix[0].length;j++){
matrix[0][j] = 0;
}
}
}
13. 字謎分組
給定一個字符串數組,將字母異位詞組合在一起。字母異位詞指字母相同,但排列不同的字符串。
示例:
輸入: ["eat", "tea", "tan", "ate", "nat", "bat"],
輸出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
說明:
所有輸入均為小寫字母。
不考慮答案輸出的順序。
解題思路:
- 怎樣判斷異位詞呢?我們之前做過判斷兩個詞是否為異位詞的題目,如果按照暴力搜索比對的話,肯定是要超時的。
- 怎樣判斷兩個詞為異位詞呢?我們采用對字符串的字節數組進行排序的方式來判斷兩個字符串是否為異位詞。
- 使用Mapl來完成分組存儲異位詞。
public List<List<String>> groupAnagrams(String[] strs) {
if (strs == null || strs.length == 0) {
return new ArrayList<List<String>>();
}
Map<String, List<String>> map = new HashMap<>();
for (int i = 0; i < strs.length; i++) {
char[] chars = strs[i].toCharArray();
Arrays.sort(chars);
String key = String.valueOf(chars);
if (map.containsKey(key) == false) {
map.put(key, new ArrayList<String>());
}
map.get(key).add(strs[i]);
}
return new ArrayList<>(map.values());
}
14. 無重復字符的最長子串
給定一個字符串,找出不含有重復字符的最長子串的長度。
示例 1:
輸入: "abcabcbb"
輸出: 3
解釋: 無重復字符的最長子串是 "abc",其長度為 3。
示例 2:
輸入: "bbbbb"
輸出: 1
解釋: 無重復字符的最長子串是 "b",其長度為 1。
解題思路:
- 核心問題是如果出現了重復,我們的start位置從何處計算。
- 因為字符串的長度不固定,因此我們使用數組來存儲字符最近一次出現的下一個位置。
public int lengthOfLongestSubstring(String s) {
int n = s.length(), ans = 0;
// current index of character
int[] index = new int[128];
// try to extend the range [i, j]
for (int j = 0, i = 0; j < n; j++) {
i = Math.max(index[s.charAt(j)], i);
ans = Math.max(ans, j - i + 1);
index[s.charAt(j)] = j + 1;
}
return ans;
}
14. 遞增的三元子序列
給定一個未排序的數組,判斷這個數組中是否存在長度為 3 的遞增子序列。
數學表達式如下:
如果存在這樣的 i, j, k, 且滿足 0 ≤ i < j < k ≤ n-1,
使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否則返回 false 。
說明: 要求算法的時間復雜度為 O(n),空間復雜度為 O(1) 。
解題思路:
- 使用兩個指針來解決問題,first代表三元子序列的第一個值,second代表三元子序列的第二個值。
- first<second<第三個值。
- 只要出現比first小的值,我們就更新first的值。
- 出現比first大比second小的值,我們更新second.
- 出現比second大的值時,三元子序列就找到了。
public boolean increasingTriplet(int[] nums) {
if (nums == null || nums.length < 3) {
return false;
}
if (nums == null || nums.length < 3) {
return false;
}
int first = Integer.MAX_VALUE, second = Integer.MAX_VALUE;
for (int num : nums) {
if (first > num) {
first = num;
} else if (first < num && second > num) {
second = num;
} else if (num > second) {
return true;
}
}
return false;
}