C++ class Solution { public: int removeElement(vector<int>& nums, int val) { int n = nums.size(); int left = 0; for (int right = 0; right < n; right++) { if (nums[right] != val) { nums[left] = nums[right]; left++; } } return left; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14
//Java class Solution { public int removeElement(int[] nums, int val) { int n = nums.length; int left = 0; for (int right = 0; right < n; right++) { if (nums[right] != val) { nums[left] = nums[right]; left++; } } return left; } }
复杂度分析
时间复杂度:O(n),其中 n 为序列的长度。我们只需要遍历该序列至多两次。
空间复杂度:O(1)。我们只需要常数的空间保存若干变量。
(2)双指针优化
思路及算法
如果要移除的元素恰好在数组的开头,例如序列 [1,2,3,4,5],当 val 为 1 时,我们需要把每一个元素都左移一位。注意到题目中说:「元素的顺序可以改变」。实际上我们可以直接将最后一个元素 5 移动到序列开头,取代元素 1,得到序列 [5,2,3,4],同样满足题目要求。这个优化在序列中 val 元素的数量较少时非常有效。
实现方面,我们依然使用双指针,两个指针初始时分别位于数组的首尾,向中间移动遍历该序列。
如果左指针 left 指向的元素等于 val,此时将右指针 right 指向的元素复制到左指针 left 的位置,然后右指针 right 左移一位。如果赋值过来的元素恰好也等于 val,可以继续把右指针 right 指向的元素的值赋值过来(左指针 left 指向的等于 val 的元素的位置继续被覆盖),直到左指针指向的元素的值不等于 val 为止。
当左指针 left 和右指针 right 重合的时候,左右指针遍历完数组中所有的元素。
这样的方法两个指针在最坏的情况下合起来只遍历了数组一次。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
C++ class Solution { public: int removeElement(vector<int>& nums, int val) { int left = 0, right = nums.size(); while (left < right) { if (nums[left] == val) { nums[left] = nums[right - 1]; right--; } else { left++; } } return left; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
//Java class Solution { public int removeElement(int[] nums, int val) { int left = 0; int right = nums.length; while (left < right) { if (nums[left] == val) { nums[left] = nums[right - 1]; right--; } else { left++; } } return left; } }
//Java class Solution { private int getNext(int n) { int totalSum = 0; while (n > 0) { int d = n % 10; n = n / 10; totalSum += d * d; } return totalSum; }
public boolean isHappy(int n) { Set<Integer> seen = new HashSet<>(); while (n != 1 && !seen.contains(n)) { seen.add(n); n = getNext(n); } return n == 1; } }
一旦两个指针都在循环中,在每个循环中,快跑者将离慢跑者更近一步。一旦快跑者落后慢跑者一步,他们就会在下一步相遇。假设循环中有 k 个数字。如果他们的起点是相隔 k−1 的位置(这是他们可以开始的最远的距离),那么快跑者需要 k−1 步才能到达慢跑者,这对于我们的目的来说也是不变的。因此,主操作仍然在计算起始 n 的下一个值,即 O(logn)。
//Java class Solution { public boolean wordPattern(String pattern, String str) { Map<String, Character> str2ch = new HashMap<String, Character>(); Map<Character, String> ch2str = new HashMap<Character, String>(); int m = str.length(); int i = 0; for (int p = 0; p < pattern.length(); ++p) { char ch = pattern.charAt(p); if (i >= m) { return false; } int j = i; while (j < m && str.charAt(j) != ' ') { j++; } String tmp = str.substring(i, j); if (str2ch.containsKey(tmp) && str2ch.get(tmp) != ch) { return false; } if (ch2str.containsKey(ch) && !tmp.equals(ch2str.get(ch))) { return false; } str2ch.put(tmp, ch); ch2str.put(ch, tmp); i = j + 1; } return i >= m; } }
复杂度分析
时间复杂度:O(n+m),其中 n 为 pattern 的长度,m 为 str 的长度。插入和查询哈希表的均摊时间复杂度均为 O(n+m)。每一个字符至多只被遍历一次。
上述两步操作的顺序不能改变,因为当遍历到下标 i 时,只能在下标 i 之前的元素中寻找与当前元素相等的元素及该元素的最大下标。
当遍历结束时,如果没有遇到两个相等元素的下标差的绝对值不超过 k,返回 false。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
//C++ class Solution { public: bool containsNearbyDuplicate(vector<int>& nums, int k) { unordered_map<int, int> dictionary; int length = nums.size(); for (int i = 0; i < length; i++) { int num = nums[i]; if (dictionary.count(num) && i - dictionary[num] <= k) { return true; } dictionary[num] = i; } return false; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//Java class Solution { public boolean containsNearbyDuplicate(int[] nums, int k) { Map<Integer, Integer> map = new HashMap<Integer, Integer>(); int length = nums.length; for (int i = 0; i < length; i++) { int num = nums[i]; if (map.containsKey(num) && i - map.get(num) <= k) { return true; } map.put(num, i); } return false; } }
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的长度。需要遍历数组一次,对于每个元素,哈希表的操作时间都是 O(1)。
空间复杂度:O(n),其中 n 是数组 nums 的长度。需要使用哈希表记录每个元素的最大下标,哈希表中的元素个数不会超过 n。
//C++ class Solution { public: bool containsNearbyDuplicate(vector<int>& nums, int k) { unordered_set<int> s; int length = nums.size(); for (int i = 0; i < length; i++) { if (i > k) { s.erase(nums[i - k - 1]); } if (s.count(nums[i])) { return true; } s.emplace(nums[i]); } return false; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
//Java class Solution { public boolean containsNearbyDuplicate(int[] nums, int k) { Set<Integer> set = new HashSet<Integer>(); int length = nums.length; for (int i = 0; i < length; i++) { if (i > k) { set.remove(nums[i - k - 1]); } if (!set.add(nums[i])) { return true; } } return false; } }
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums的长度。需要遍历数组一次,对于每个元素,哈希集合的操作时间都是 O(1)。
空间复杂度:O(k),其中 k 是判断重复元素时允许的下标差的绝对值的最大值。需要使用哈希集合存储滑动窗口中的元素,任意时刻滑动窗口中的元素个数最多为 k+1 个。
此题是需要我们判断 s 和 t 每个位置上的字符是否都一一对应,即 s 的任意一个字符被 t 中唯一的字符对应,同时 t 的任意一个字符被 s 中唯一的字符对应。这也被称为「双射」的关系。
以示例 2 为例,t 中的字符 a 和 r 虽然有唯一的映射 o,但对于 s 中的字符 o 来说其存在两个映射 {a,r},故不满足条件。
因此,我们维护两张哈希表,第一张哈希表 s2t 以 s 中字符为键,映射至 t 的字符为值,第二张哈希表 t2s 以 t 中字符为键,映射至 s 的字符为值。从左至右遍历两个字符串的字符,不断更新两张哈希表,如果出现冲突(即当前下标 index 对应的字符 s[index] 已经存在映射且不为 t[index] 或当前下标 index 对应的字符 t[index] 已经存在映射且不为 s[index])时说明两个字符串无法构成同构,返回 false。
如果遍历结束没有出现冲突,则表明两个字符串是同构的,返回 true 即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
//C++ class Solution { public: bool isIsomorphic(string s, string t) { unordered_map<char, char> s2t; unordered_map<char, char> t2s; int len = s.length(); for (int i = 0; i < len; ++i) { char x = s[i], y = t[i]; if ((s2t.count(x) && s2t[x] != y) || (t2s.count(y) && t2s[y] != x)) { return false; } s2t[x] = y; t2s[y] = x; } return true; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
//Java class Solution { public boolean isIsomorphic(String s, String t) { Map<Character, Character> s2t = new HashMap<Character, Character>(); Map<Character, Character> t2s = new HashMap<Character, Character>(); int len = s.length(); for (int i = 0; i < len; ++i) { char x = s.charAt(i), y = t.charAt(i); if ((s2t.containsKey(x) && s2t.get(x) != y) || (t2s.containsKey(y) && t2s.get(y) != x)) { return false; } s2t.put(x, y); t2s.put(y, x); } return true; } }
从另一个角度考虑,t 是 s 的异位词等价于「两个字符串中字符出现的种类和次数均相等」。由于字符串只包含 26 个小写字母,因此我们可以维护一个长度为 26的频次数组 table,先遍历记录字符串 s 中字符出现的频次,然后遍历字符串 t,减去 table 中对应的频次,如果出现 table[i]<0,则说明 t 包含一个不在 s 中的额外字符,返回 false 即可。
//C++ class Solution { public: int longestConsecutive(vector<int>& nums) { unordered_set<int> num_set; for (const int& num : nums) { num_set.insert(num); }
int longestStreak = 0;
for (const int& num : num_set) { if (!num_set.count(num - 1)) { int currentNum = num; int currentStreak = 1;
//Java class Solution { public int longestConsecutive(int[] nums) { Set<Integer> num_set = new HashSet<Integer>(); for (int num : nums) { num_set.add(num); }
int longestStreak = 0;
for (int num : num_set) { if (!num_set.contains(num - 1)) { int currentNum = num; int currentStreak = 1;
//C++ class Solution { public: void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) { for (int i = 0; i != n; ++i) { nums1[m + i] = nums2[i]; } sort(nums1.begin(), nums1.end()); } };
1 2 3 4 5 6 7 8 9
//Java class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { for (int i = 0; i != n; ++i) { nums1[m + i] = nums2[i]; } Arrays.sort(nums1); } }
//C++ class Solution { public: void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) { int p1 = 0, p2 = 0; int sorted[m + n]; int cur; while (p1 < m || p2 < n) { if (p1 == m) { cur = nums2[p2++]; } else if (p2 == n) { cur = nums1[p1++]; } else if (nums1[p1] < nums2[p2]) { cur = nums1[p1++]; } else { cur = nums2[p2++]; } sorted[p1 + p2 - 1] = cur; } for (int i = 0; i != m + n; ++i) { nums1[i] = sorted[i]; } } };
//Java class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int p1 = 0, p2 = 0; int[] sorted = new int[m + n]; int cur; while (p1 < m || p2 < n) { if (p1 == m) { cur = nums2[p2++]; } else if (p2 == n) { cur = nums1[p1++]; } else if (nums1[p1] < nums2[p2]) { cur = nums1[p1++]; } else { cur = nums2[p2++]; } sorted[p1 + p2 - 1] = cur; } for (int i = 0; i != m + n; ++i) { nums1[i] = sorted[i]; } } }
//C++ class Solution { public: void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) { int p1 = m - 1, p2 = n - 1; int tail = m + n - 1; int cur; while (p1 >= 0 || p2 >= 0) { if (p1 == -1) { cur = nums2[p2--]; } else if (p2 == -1) { cur = nums1[p1--]; } else if (nums1[p1] > nums2[p2]) { cur = nums1[p1--]; } else { cur = nums2[p2--]; } nums1[tail--] = cur; } } };
//Java class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int p1 = m - 1, p2 = n - 1; int tail = m + n - 1; int cur; while (p1 >= 0 || p2 >= 0) { if (p1 == -1) { cur = nums2[p2--]; } else if (p2 == -1) { cur = nums1[p1--]; } else if (nums1[p1] > nums2[p2]) { cur = nums1[p1--]; } else { cur = nums2[p2--]; } nums1[tail--] = cur; } } }