Leetcode數據結構與算法
[0001]求1+2+…+n
求 1+2+...+n
,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
示例 1:
輸入: n = 3
輸出: 6
示例 2:
輸入: n = 9
輸出: 45
限制:
1 <= n <= 10000
解答:
等差數列求和公式
class Solution {
public int sumNums(int n) {
return (int) (Math.pow(n, 2) + n) >> 1;
}
}
遞歸
class Solution {
public int sumNums(int n) {
int sum = n;
boolean flag = n > 0 && (sum += sumNums(n - 1)) > 0;
return sum;
}
}
[0002]反轉鏈表
反轉一個單鏈表。
示例:
輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
進階:
你可以迭代或遞歸地反轉鏈表。你能否用兩種方法解決這道題?
方法一:迭代
假設存在鏈表 1 → 2 → 3 → Ø,我們想要把它改成 Ø ← 1 ← 2 ← 3。
在遍歷列表時,將當前節點的 next 指針改爲指向前一個元素。由於節點沒有引用其上一個節點,因此必須事先存儲其前一個元素。在更改引用之前,還需要另一個指針來存儲下一個節點。不要忘記在最後返回新的頭引用!
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
public ListNode reverseList(ListNode head) {
//申請節點,pre和 cur,pre指向null
ListNode pre = null;
ListNode cur = head;
ListNode tmp = null;
while (cur != null) {
tmp = cur.next;//記錄當前節點的下一個節點
cur.next = pre;//然後將當前節點指向pre
//pre和cur節點都前進一位
pre = cur;
cur = tmp;
}
return pre;
}
複雜度分析
- 時間複雜度:O(n),假設 nn 是列表的長度,時間複雜度是 O(n)。
- 空間複雜度:O(1)。
方法二:遞歸
遞歸的兩個條件:
- 終止條件是當前節點或者下一個節點==null
- 在函數內部,改變節點的指向,也就是 head 的下一個節點指向 head 遞歸函數那句
head.next.next = head
很不好理解,其實就是 head 的下一個節點指向head。
遞歸函數中每次返回的 cur 其實只最後一個節點,在遞歸函數內部,改變的是當前節點的指向。
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}
複雜度分析
-
時間複雜度:O(n),假設 n 是列表的長度,那麼時間複雜度爲O(n)。
-
空間複雜度:O(n),由於使用遞歸,將會使用隱式棧空間。遞歸深度可能會達到 n 層。
[0003]兩數之和
給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。
你可以假設每種輸入只會對應一個答案。但是,你不能重複利用這個數組中同樣的元素。
示例:
給定 nums = [2, 7, 11, 15], target = 9
因爲 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
方法一:暴力法
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] == target - nums[i]) {
return new int[] { i, j };
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
}
時間複雜度:O(n2)
空間複雜度:O(1)
方法二:兩遍哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement) && map.get(complement) != i) {
return new int[] { i, map.get(complement) };
}
}
throw new IllegalArgumentException("No two sum solution");
}
}
時間複雜度:O(n)
空間複雜度:O(n)
方法三:一遍哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.n(complement)) {
return new int[] {, i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
時間複雜度:O(n)
空間複雜度:O(n)
[0004]合併排序的數組
給定兩個排序後的數組 A 和 B,其中 A 的末端有足夠的緩衝空間容納 B。 編寫一個方法,將 B 合併入 A 並排序。
初始化 A 和 B 的元素數量分別爲 m 和 n。
示例:
輸入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6], n = 3
輸出: [1,2,2,3,5,6]
方法 1: 直接合並後排序
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
//System.arraycopy(B, 0 , A, m, n);
for (int i = 0; i != n; ++i)
A[m + i] = B[i];
Arrays.sort(A);
}
}
時間複雜度:O((m+n)log(m+n))
空間複雜度:O(log(m+n))
方法 2: 雙指針
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
int pa = 0, pb = 0;
int sorted[] = new int[m + n];
int cur;
while (pa < m || pb < n) {
if (pa == m)
cur = B[pb++];
else if (pb == n)
cur = A[pa++];
else if (A[pa] < B[pb])
cur = A[pa++];
else
cur = B[pb++];
sorted[pa + pb - 1] = cur;
}
for (int i = 0; i != m + n; ++i)
A[i] = sorted[i];
}
}
時間複雜度:O(m+n)
空間複雜度:O(m+n)
方法3:逆向雙指針
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
// 將其中一個數組中的數字遍歷完
while (m > 0 && n > 0) {
// 對比選出較大的數放在 m + n - 1 的位置,並將選出此數的指針向前移動
A[m + n - 1] = A[m - 1] > B[n - 1] ? A[m-- - 1] : B[n-- - 1];
}
// 剩下的數都比已經遍歷過的數小
// 如果 m 不爲 0,則 A 沒遍歷完,都已經在 A 中不用再管
// 如果 n 不爲 0,則 B 沒遍歷完,直接全移到 A 中相同的位置
while (n > 0) {
A[n - 1] = B[n - 1];
n--;
}
}
}
時間複雜度:O(m+n)
空間複雜度:O(1)
[0005]分糖果 II
排排坐,分糖果。
我們買了一些糖果 candies,打算把它們分給排好隊的 n = num_people 個小朋友。
給第一個小朋友 1 顆糖果,第二個小朋友 2 顆,依此類推,直到給最後一個小朋友 n 顆糖果。
然後,我們再回到隊伍的起點,給第一個小朋友 n + 1 顆糖果,第二個小朋友 n + 2 顆,依此類推,直到給最後一個小朋友 2 * n 顆糖果。
重複上述過程(每次都比上一次多給出一顆糖果,當到達隊伍終點後再次從隊伍起點開始),直到我們分完所有的糖果。注意,就算我們手中的剩下糖果數不夠(不比前一次發出的糖果多),這些糖果也會全部發給當前的小朋友。
返回一個長度爲 num_people、元素之和爲 candies 的數組,以表示糖果的最終分發情況(即 ans[i] 表示第 i 個小朋友分到的糖果數)。
示例 1:
輸入:candies = 7, num_people = 4
輸出:[1,2,3,1]
解釋:
第一次,ans[0] += 1,數組變爲 [1,0,0,0]。
第二次,ans[1] += 2,數組變爲 [1,2,0,0]。
第三次,ans[2] += 3,數組變爲 [1,2,3,0]。
第四次,ans[3] += 1(因爲此時只剩下 1 顆糖果),最終數組變爲 [1,2,3,1]。
示例 2:
輸入:candies = 10, num_people = 3
輸出:[5,2,3]
解釋:
第一次,ans[0] += 1,數組變爲 [1,0,0]。
第二次,ans[1] += 2,數組變爲 [1,2,0]。
第三次,ans[2] += 3,數組變爲 [1,2,3]。
第四次,ans[0] += 4,最終數組變爲 [5,2,3]。
提示:
1 <= candies <= 10^9
1 <= num_people <= 1000
方法 1: 暴力
思路
最直觀的方法是不斷地遍歷數組,如果還有糖就一直分,直到沒有糖爲止。
class Solution {
public int[] distributeCandies(int candies, int num_people) {
int[] ans = new int[num_people];
int i = 0;
while (candies != 0) {
ans[i % num_people] += Math.min(candies, i + 1);
candies -= Math.min(candies, i + 1);
i += 1;
}
return ans;
}
}
複雜度
方法 2:等差數列求和
思路
這是一個數學問題,可以對其簡化。
更好的做法是使用一個簡單的公式代表糖果分配,可以在 O(N) 時間內完成糖果分發,並生成最終的分配數組。
逐步推導該公式:
https://leetcode-cn.com/problems/distribute-candies-to-people/solution/fen-tang-guo-ii-by-leetcode-solution/
https://leetcode-cn.com/problems/distribute-candies-to-people/solution/xiang-xi-jie-shi-shu-xue-fang-fa-zen-yao-zuo-gao-z/
class Solution {
public int[] distributeCandies(int candies, int num_people) {
int n = num_people;
// how many people received complete gifts
int p = (int)(Math.sqrt(2 * candies + 0.25) - 0.5);
int remaining = (int)(candies - (p + 1) * p * 0.5);
int rows = p / n, cols = p % n;
int[] d = new int[n];
for(int i = 0; i < n; ++i) {
// complete rows
d[i] = (i + 1) * rows + (int)(rows * (rows - 1) * 0.5) * n;
// cols in the last row
if (i < cols) d[i] += i + 1 + rows * n;
}
// remaining candies
d[cols] += remaining;
return d;
}
}
複雜度分析
時間複雜度:O(N),計算 N 個人的糖果數量。
空間複雜度:O(1),除了答案數組只需要常數空間來存儲若干變量。
[0006]有多少小於當前數字的數字
給你一個數組 nums,對於其中每個元素 nums[i],請你統計數組中比它小的所有數字的數目。
換而言之,對於每個 nums[i] 你必須計算出有效的 j 的數量,其中 j 滿足 j != i 且 nums[j] < nums[i] 。
以數組形式返回答案。
示例 1:
輸入:nums = [8,1,2,2,3]
輸出:[4,0,1,1,3]
解釋:
對於 nums[0]=8 存在四個比它小的數字:(1,2,2 和 3)。
對於 nums[1]=1 不存在比它小的數字。
對於 nums[2]=2 存在一個比它小的數字:(1)。
對於 nums[3]=2 存在一個比它小的數字:(1)。
對於 nums[4]=3 存在三個比它小的數字:(1,2 和 2)。
示例 2:
輸入:nums = [6,5,4,8]
輸出:[2,1,0,3]
示例 3:
輸入:nums = [7,7,7,7]
輸出:[0,0,0,0]
提示:
2 <= nums.length <= 500
0 <= nums[i] <= 100
方法一:暴力
class Solution {
public int[] smallerNumbersThanCurrent(int[] nums) {
int[] res = new int[nums.length];
for(int i = 0;i < nums.length; i++)
for(int j = 0;j < nums.length; j++)
if(nums[i] > nums[j])
res[i]++;
return res;
}
}
複雜度分析
時間複雜度:枚舉數組裏的每個數字爲 O(n) ,遍歷數組也爲 O(n),所以總時間複雜度爲兩者相乘,即 O(n2) ,其中 n=nums.length 。
空間複雜度:O(1) ,不需要使用額外的空間。
方法二:頻次數組 + 前綴和
https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/solution/you-duo-shao-xiao-yu-dang-qian-shu-zi-de-shu-zi–2/
class Solution {
public int[] smallerNumbersThanCurrent(int[] nums) {
int[] res = new int[nums.length];
int[] temp = new int[101];
for(int i = 0;i < nums.length; i++)
temp[nums[i]]++;
for(int i = 1;i < temp.length; i++)
temp[i] += temp[i-1]; // 求前綴和
for(int i = 0;i < nums.length; i++)
if (nums[i]!=0)
res[i] = temp[nums[i] - 1];
return res;
}
}
時間複雜度:O(S+n) ,其中 S 爲值域大小,n=nums.length 。
空間複雜度:O(S) ,需要開一個值域大小的數組。
方法三:排序
public int[] smallerNumbersThanCurrent(int[] nums) { // 8, 1, 2, 2, 3
int len = nums.length;
Map<Integer, Set<Integer>> valueIndex = new HashMap<>(len); // 預存每個值與索引對應
for (int i = 0; i < len; i++) {
if (!valueIndex.containsKey(nums[i])) valueIndex.put(nums[i], new HashSet<>());
valueIndex.get(nums[i]).add(i);
}
int[] sortedArr = Arrays.copyOf(nums, len);
int[] res = new int[len];
Arrays.sort(sortedArr); // 1, 2, 2, 3, 8
for (int si = len - 1; si >= 0; si--) {
for (int i : valueIndex.get(sortedArr[si])) res[i] = si; // 同值的所有索引都更新
}
return res;
}
時間複雜度 O(nlog(n)),空間複雜度 O(n)
[0007]和爲s的連續正數序列
輸入一個正整數 target ,輸出所有和爲 target 的連續正整數序列(至少含有兩個數)。
序列內的數字由小到大排列,不同序列按照首個數字從小到大排列。
https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/mian-shi-ti-57-ii-he-wei-sde-lian-xu-zheng-shu-x-2/
示例 1:
輸入:target = 9
輸出:[[2,3,4],[4,5]]
示例 2:
輸入:target = 15
輸出:[[1,2,3,4,5],[4,5,6],[7,8]]
限制:
1 <= target <= 10^5
方法一:枚舉 + 暴力
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> list = new ArrayList<>();
// (target - 1) / 2 等效於 target / 2 下取整
int sum = 0, limit = (target - 1) / 2;
for (int i = 1; i <= limit; ++i) {
for (int j = i;; ++j) {
sum += j;
if (sum > target) {
sum = 0;
break;
}
else if (sum == target) {
int[] arr = new int[j-i+1];
for (int k = i; k <= j; k++) {
arr[k-i] = k;
}
list.add(arr);
sum = 0;
break;
}
}
}
return list.toArray(new int[list.size()][]);
}
}
方法二:枚舉 + 數學優化
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> list = new ArrayList<>();
// (target - 1) / 2 等效於 target / 2 下取整
int limit = (target - 1) / 2;
for (int x = 1; x <= limit; ++x) {
long delta = 1 - 4 * (x - 1L * x * x - 2 * target);
if (delta < 0) continue;
int delta_sqrt = (int)Math.sqrt(delta + 0.5);
if (1L * delta_sqrt * delta_sqrt == delta
&& (delta_sqrt - 1) % 2 == 0){
// 另一個解(-1-delta_sqrt)/2必然小於0,不用考慮
int y = (-1 + delta_sqrt) / 2;
if (x < y) {
int[] arr = new int[y-x+1];
for (int i = x; i <= y; i++){
arr[i-x] = i;
}
list.add(arr);
}
}
}
return list.toArray(new int[list.size()][]);
}
}
方法三:雙指針(滑動窗口)
https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/
public int[][] findContinuousSequence(int target) {
int i = 1; // 滑動窗口的左邊界
int j = 1; // 滑動窗口的右邊界
int sum = 0; // 滑動窗口中數字的和
List<int[]> res = new ArrayList<>();
while (i <= target / 2) {
if (sum < target) {
// 右邊界向右移動
sum += j;
j++;
} else if (sum > target) {
// 左邊界向右移動
sum -= i;
i++;
} else {
// 記錄結果
int[] arr = new int[j-i];
for (int k = i; k < j; k++) {
arr[k-i] = k;
}
res.add(arr);
// 右邊界向右移動
sum -= i;
i++;
}
}
return res.toArray(new int[res.size()][]);
}
[0008]左旋轉字符串
字符串的左旋轉操作是把字符串前面的若干個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操作的功能。比如,輸入字符串"abcdefg"和數字2,該函數將返回左旋轉兩位得到的結果"cdefgab"。
示例 1:
輸入: s = "abcdefg", k = 2
輸出: "cdefgab"
示例 2:
輸入: s = "lrloseumgh", k = 6
輸出: "umghlrlose"
限制:
1 <= k < s.length <= 10000
方法一:
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n) + s.substring(0,n);
}
}
方法二:翻轉reverse
class Solution {
public String reverseLeftWords(String s, int n) {
char[] chars = s.toCharArray();
reverse(chars, 0, n-1);
reverse(chars, n, chars.length-1);
reverse(chars, 0, chars.length-1);
return new String(chars);
}
private void reverse(char[] arr, int i, int j) {
while(i < j) {
char x = arr[i];
arr[i] = arr[j];
arr[j] = x;
i++;
j--;
}
}
}
- 時間:遍歷數組,O(N)
- 空間:新開闢數組,O(N)
[0009]將數字變成 0 的操作次數
給你一個非負整數 num
,請你返回將它變成 0 所需要的步數。 如果當前數字是偶數,你需要把它除以 2 ;否則,減去 1 。
示例 1:
輸入:num = 14
輸出:6
解釋:
步驟 1) 14 是偶數,除以 2 得到 7 。
步驟 2) 7 是奇數,減 1 得到 6 。
步驟 3) 6 是偶數,除以 2 得到 3 。
步驟 4) 3 是奇數,減 1 得到 2 。
步驟 5) 2 是偶數,除以 2 得到 1 。
步驟 6) 1 是奇數,減 1 得到 0 。
示例 2:
輸入:num = 8
輸出:4
解釋:
步驟 1) 8 是偶數,除以 2 得到 4 。
步驟 2) 4 是偶數,除以 2 得到 2 。
步驟 3) 2 是偶數,除以 2 得到 1 。
步驟 4) 1 是奇數,減 1 得到 0 。
示例 3:
輸入:num = 123
輸出:12
- 奇偶判斷。
- 奇數自減1。
- 對
~1
按位與&
- 直接對
1
按位異或^
- 對
方法一:迭代
public int numberOfSteps(int num) {
int step = 0; //記錄步數
while (num > 0) {
if ((num & 1) == 0) {//num爲偶數
num >>= 1;
}
else { //num爲奇數
num &= ~1; //等價於 num ^= 1
}
step++;
}
return step;
}
- 時間複雜度:O(1),num是個常數。
- 空間複雜度:O(1),使用常數級別的空間。
方法二:遞歸
public int numberOfSteps1(int num) {
if(num == 0)
return 0;
if((num & 1) == 0) { // num爲偶數
return numberOfSteps1(num >>> 1) + 1;
}
else { // num爲奇數
return numberOfSteps1(num - 1) + 1;
}
}
- 時間複雜度:O(1)
- 空間複雜度:O(1)
[0010]解壓縮編碼列表
給你一個以行程長度編碼壓縮的整數列表 nums 。
考慮每對相鄰的兩個元素freq, val] = [nums[2*i], nums[2*i+1]]
(其中 i >= 0 ),每一對都表示解壓後子列表中有 freq 個值爲 val 的元素,你需要從左到右連接所有子列表以生成解壓後的列表。
請你返回解壓後的列表。
示例:
輸入:nums = [1,2,3,4]
輸出:[2,4,4,4]
解釋:第一對 [1,2] 代表着 2 的出現頻次爲 1,所以生成數組 [2]。
第二對 [3,4] 代表着 4 的出現頻次爲 3,所以生成數組 [4,4,4]。
最後將它們串聯到一起 [2] + [4,4,4] = [2,4,4,4]。
示例 2:
輸入:nums = [1,1,2,3]
輸出:[1,3,3]
提示:
2 <= nums.length <= 100
nums.length % 2 == 0
1 <= nums[i] <= 100
class Solution {
public int[] decompressRLElist(int[] nums) {
int length = 0;
for(int i=0;i<nums.length;i+=2){
length += nums[i];
}
int[] result = new int[length];
// 新數組角標
int index = 0;
for(int i = 0; i < nums.length; i+=2){
// 填充a個b,每填充一次,a-1,index+1
int a = nums[i];
while(a > 0){
result[index] = nums[i+1];
a--;
index++;
}
}
return result;
}
}
- 時間複雜度:O(n)
- 空間複雜度:O(1)
[0011]整數的各位積和之差
給你一個整數 n
,請你幫忙計算並返回該整數「各位數字之積」與「各位數字之和」的差。
示例 1:
輸入:n = 234
輸出:15
解釋:
各位數之積 = 2 * 3 * 4 = 24
各位數之和 = 2 + 3 + 4 = 9
結果 = 24 - 9 = 15
示例 2:
輸入:n = 4421
輸出:21
解釋:
各位數之積 = 4 * 4 * 2 * 1 = 32
各位數之和 = 4 + 4 + 2 + 1 = 11
結果 = 32 - 11 = 21
解答:
class Solution {
public int subtractProductAndSum(int n) {
int add = 0, mul = 1;
while (n > 0) {
int digit = n % 10;
n /= 10;
add += digit;
mul *= digit;
}
return mul - add;
}
}
-
時間複雜度:O(logN)
-
空間複雜度:O(1)
[0012]猜數字
小A 和 小B 在玩猜數字。小B 每次從 1, 2, 3 中隨機選擇一個,小A 每次也從 1, 2, 3 中選擇一個猜。他們一共進行三次這個遊戲,請返回 小A 猜對了幾次?
輸入的guess數組爲 小A 每次的猜測,answer數組爲 小B 每次的選擇。guess和answer的長度都等於3。
示例 1:
輸入:guess = [1,2,3], answer = [1,2,3]
輸出:3
解釋:小A 每次都猜對了。
示例 2:
輸入:guess = [2,2,3], answer = [3,2,1]
輸出:1
解釋:小A 只猜對了第二次。
限制:
guess的長度 = 3
answer的長度 = 3
guess的元素取值爲 {1, 2, 3} 之一。
answer的元素取值爲 {1, 2, 3} 之一。
解答:
class Solution {
public int game(int[] guess, int[] answer) {
int n=0;//猜中的個數
for(int i=0;i<3;i++)
if(guess[i]==answer[i])
n++;
return n;
}
}
[0013]統計位數爲偶數的數字
給你一個整數數組 nums,請你返回其中位數爲 偶數 的數字的個數。
示例 1:
輸入:nums = [12,345,2,6,7896]
輸出:2
解釋:
12 是 2 位數字(位數爲偶數)
345 是 3 位數字(位數爲奇數)
2 是 1 位數字(位數爲奇數)
6 是 1 位數字 位數爲奇數)
7896 是 4 位數字(位數爲偶數)
因此只有 12 和 7896 是位數爲偶數的數字
示例 2:
輸入:nums = [555,901,482,1771]
輸出:1
解釋:
只有 1771 是位數爲偶數的數字。
提示:
1 <= nums.length <= 500
1 <= nums[i] <= 10^5
方法一:枚舉 + 字符串
class Solution {
public int findNumbers(int[] nums) {
int result = 0;
for(int i = 0; i<nums.length;i++){
result += String.valueOf(nums[i]).length() % 2 == 0 ? 1 : 0;
}
return result;
}
}
時間複雜度:O(N)。這裏假設將整數轉換爲字符串的時間複雜度爲 O(1)。
空間複雜度:O(1)
方法二:範圍已知
class Solution {
public int findNumbers(int[] nums) {
int result = 0;
for(int i = 0; i<nums.length;i++){
if((nums[i]>=10&&nums[i]<100)||(nums[i]>=1000&&nums[i]<10000))
result++;
}
return result;
}
}
時間複雜度:O(N)
空間複雜度:O(1)
方法三:枚舉 + 數學
class Solution {
public int findNumbers(int[] nums) {
int result = 0;
for(int num:nums){
//logx(y) =loge(y) / loge(x)
if ((int)(Math.log(num)/Math.log(10) + 1) % 2 == 0)
result++;
}
return result;
}
}
時間複雜度:O(N)
空間複雜度:O(1)
[0014]寶石與石頭
給定字符串J 代表石頭中寶石的類型,和字符串 S代表你擁有的石頭。 S 中每個字符代表了一種你擁有的石頭的類型,你想知道你擁有的石頭中有多少是寶石。
J 中的字母不重複,J 和 S中的所有字符都是字母。字母區分大小寫,因此"a"和"A"是不同類型的石頭。
示例 1:
輸入: J = "aA", S = "aAAbbbb"
輸出: 3
示例 2:
輸入: J = "z", S = "ZZ"
輸出: 0
注意:
S 和 J 最多含有50個字母。
J 中的字符不重複。
方法一: 暴力法
class Solution {
public int numJewelsInStones(String J, String S) {
int ans = 0;
for (char s: S.toCharArray()) // For each stone...
for (char j: J.toCharArray()) // For each jewel...
if (j == s) { // If the stone is a jewel...
ans++;
break; // Stop searching whether this stone 's' is a jewel
}
return ans;
}
}
時間複雜度:O(J.length * S.length))。
空間複雜度:在 Java 實現中,空間複雜度爲 O(J.length∗S.length))。
方法二: 哈希集合
class Solution {
public int numJewelsInStones(String J, String S) {
Set<Character> Jset = new HashSet();
for (char j: J.toCharArray())
Jset.add(j);
int ans = 0;
for (char s: S.toCharArray())
if (Jset.contains(s))
ans++;
return ans;
}
}
時間複雜度:O(J.length + S.length))。
空間複雜度:O(J.length)
方法三:位運算
解決類似 Set 或 boolean[] 的、表示有或無二選一情況時都可考慮
注意共多少種情況,此處 int 的32位不夠
class Solution {
public int numJewelsInStones(String J, String S) {
long jewels = 0b0L;
for (char c : J.toCharArray()) jewels |= 1L << (c - 'A');
int count = 0;
for (char c : S.toCharArray()) count += (jewels >> (c - 'A')) & 1;
return count;
}
}
方法四:byte
class Solution {
public int numJewelsInStones(String J, String S) {
byte[] arr = new byte[58];
int count = 0;
for (char ch : J.toCharArray()) {
arr[ch - 65] = 1;
}
for (char ch : S.toCharArray()) {
if(arr[ch -65] == 1) {
count++;
};
}
return count;
}
}
[0015]IP 地址無效化
給你一個有效的 IPv4 地址 address,返回這個 IP 地址的無效化版本。
所謂無效化 IP 地址,其實就是用 “[.]” 代替了每個 “.”。
示例 1:
輸入:address = "1.1.1.1"
輸出:"1[.]1[.]1[.]1"
示例 2:
輸入:address = "255.100.50.0"
輸出:"255[.]100[.]50[.]0"
提示:
給出的 address 是一個有效的 IPv4 地址
方法一:
class Solution {
public String defangIPaddr(String address) {
return address.replace(".","[.]");
}
}
方法二:
class Solution {
public String defangIPaddr(String address) {
StringBuilder s = new StringBuilder(address);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
s.insert(i + 1, ']');// 先插入後面,此時 i 下標仍是'.'
s.insert(i, '[');// 插入 '.' 前面,此時 i 下標是'[' ,i+2 下標爲']'
i += 3;// 故 i 直接加 3,爲下一個字符,注意此時已經是原來 i+1 下標的字符了;
//此次循環結束進入下次循環還會進行加 1,不過又因爲 ip 地址格式的原因,不會有連續的兩個 '.' 連着;
//所以這個位置絕不可能是 '.',所以再加 1,也沒問題。
}
}
return s.toString();
}
}
方法三:
class Solution {
public String defangIPaddr(String address) {
StringBuilder s = new StringBuilder();
for (int i = 0; i < address.length(); i++) {
if (address.charAt(i) == '.') {
s.append("[.]");
} else {
s.append(address.charAt(i));
}
}
return s.toString();
}
}
[0016]TinyURL 的加密與解密
TinyURL是一種URL簡化服務, 比如:當你輸入一個URL https://leetcode.com/problems/design-tinyurl 時,它將返回一個簡化的URL http://tinyurl.com/4e9iAk.
要求:設計一個 TinyURL 的加密 encode 和解密 decode 的方法。你的加密和解密算法如何設計和運作是沒有限制的,你只需要保證一個URL可以被加密成一個TinyURL,並且這個TinyURL可以用解密方法恢復成原本的URL。
方法 1:使用簡單的計數
爲了加密 URL,我們使用計數器 (i) ,每遇到一個新的 URL 都加一。我們將 URL 與它的次數 i 放在哈希表 HashMap 中,這樣我們在稍後的解密中可以輕易地獲得原本的 URL。
public class Codec {
Map<Integer, String> map = new HashMap<>();
int i = 0;
public String encode(String longUrl) {
map.put(i, longUrl);
return "http://tinyurl.com/" + i++;
}
public String decode(String shortUrl) {
return map.get(Integer.parseInt(shortUrl.replace("http://tinyurl.com/", "")));
}
}
表現分析
可以加密解密的 URL 數目受限於 int 所能表示的範圍。
如果超過 int 個 URL 需要被加密,那麼超過範圍的整數會覆蓋之前存儲的 URL,導致算法失效。
URL 的長度不一定比輸入的 longURL 短。它只與加密的 URL 被加密的順序有關。
這個方法的問題是預測下一個會產生的加密 URL 非常容易,因爲產生幾個 URL 後很容易推測出生成的模式。
方法 2:使用出現次序加密
算法
這種方法中,我們將當前 URL 第幾個出現作爲關鍵字進行加密,將這個出現次序看做 62 進制,並將每一位映射到一個長度爲 62 位的表中對應的字母作爲哈希值。此方法中,我們使用一系列整數和字母表來加密,而不是僅僅使用數字進行加密。
public class Codec {
String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
HashMap<String, String> map = new HashMap<>();
int count = 1;
public String getString() {
int c = count;
StringBuilder sb = new StringBuilder();
while (c > 0) {
c--;
sb.append(chars.charAt(c % 62));
c /= 62;
}
return sb.toString();
}
public String encode(String longUrl) {
String key = getString();
map.put(key, longUrl);
count++;
return "http://tinyurl.com/" + key;
}
public String decode(String shortUrl) {
return map.get(shortUrl.replace("http://tinyurl.com/", ""));
}
}
表現分析
可加密的 URL 數目還是依賴於int 的範圍。因爲相同的 count 在出現次序溢出整數範圍後仍然會出現。
加密後 URL 的長度不一定更短,但某種程度上與longURL 的出現次序相對獨立。比方說產生的 URL 長度按順序會是 1(62次),2(62次)。
這個算法的表現比較好,因爲相同的加密結果只有在溢出整數後纔會發生,這個範圍非常大。
如果出現重複,下一次產生的加密結果還是能通過某種計算被預測出來。
方法 3:使用hashcode
這種方法中,我們使用一種內建函數hashCode() 來爲每一個 URL 產生加密結果。同樣的,映射結果保存在 HashMap 中以供解碼。
public class Codec {
Map<Integer, String> map = new HashMap<>();
public String encode(String longUrl) {
map.put(longUrl.hashCode(), longUrl);
return "http://tinyurl.com/" + longUrl.hashCode();
}
public String decode(String shortUrl) {
return map.get(Integer.parseInt(shortUrl.replace("http://tinyurl.com/", "")));
}
}
表現分析
可加密 URL 的數目由 int 決定,因爲 hashCode 使用整數運算。
加密後 URL 的平均長度與 longURL 的長度沒有直接關聯。
hashCode()對於不同的字符串不一定產生獨一無二的加密後 URL。像這樣對於不同輸入產生相同輸出的過程叫做衝突。因此,如果加密字符串的數目增加,衝突的概率也會增加,最終導致算法失效。
可能幾個字符串加密後衝突就會發生,會遠比 int 要小。這與生日悖論類似,也就是如果有23個人,存在 2 個人同一天生日的概率達到 50%,如果有 70 個人,這一概率會高達 99.9%。
這種方法中,很難根據前面產生的 URL 結果預測後面加密 URL 的答案。
方法 4:使用隨機數 [Accepted]
算法
這個方法中,我們使用隨機整數來加密。爲了防止產生的結果與之前某個 longURL 產生的結果相同,我們生成一個新的隨機數作爲加密結果。這個數據存在哈希表 HashMap 中,以便解碼。
public class Codec {
Map<Integer, String> map = new HashMap<>();
Random r = new Random();
int key = r.nextInt(Integer.MAX_VALUE);
public String encode(String longUrl) {
while (map.containsKey(key)) {
key = r.nextInt(Integer.MAX_VALUE);
}
map.put(key, longUrl);
return "http://tinyurl.com/" + key;
}
public String decode(String shortUrl) {
return map.get(Integer.parseInt(shortUrl.replace("http://tinyurl.com/", "")));
}
}
表現分析
能被加密的 URL 數目受限於 int。
加密 URL 的平均長度與 longURL 的長度無關,因爲使用了隨機整數。
URL 的長度不一定比輸入的 longURL 短。只與 URL 加密的相對順序有關。
由於加密過程中使用了隨機數,就像前面的算法所述,當輸入字符串的數目增加時,衝突的次數也會增加,導致算法失效。
由於使用了隨機數,想根據產生的 URL 推測出加密算法是不可能的。
方法 5:隨機固定長度加密
算法
在這種方法中,我們像方法 2 一樣再次使用數字和字母表集合來爲 URL 生成加密結果。這種方法中,加密後的長度固定是 6 位。如果產生出來的加密結果與之前產生的結果一樣,就換一個新的加密結果。
public class Codec {
String alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
HashMap<String, String> map = new HashMap<>();
Random rand = new Random();
String key = getRand();
public String getRand() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
sb.append(alphabet.charAt(rand.nextInt(62)));
}
return sb.toString();
}
public String encode(String longUrl) {
while (map.containsKey(key)) {
key = getRand();
}
map.put(key, longUrl);
return "http://tinyurl.com/" + key;
}
public String decode(String shortUrl) {
return map.get(shortUrl.replace("http://tinyurl.com/", ""));
}
}
表現分析
可加密的 URL 數目非常大
加密 URL 的長度固定是 6,這相比於能加密的字符串數目是極大的縮減優化。
這個方法的表現非常好,因爲幾乎不可能產生相同加密結果。
我們也可以通過增加加密字符串的長度來增加加密結果的數目。因此,在加密字符串的長度和可加密的字符串數目之間我們需要做一個權衡。