1. 两数之和 简单
给定一个整数数组 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) {
Map<Integer,Integer> map=new HashMap<>();
for (int i = 0; i < nums.length; i++) {
for (int j = i+1; j <nums.length ; j++) {
if(target-nums[i]==nums[j]){
return new int[]{i,j};
}
}
}
return null;
}
}
利用map
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map=new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int param=target-nums[i];//2 7
if(map.containsKey(param)){
return new int[]{map.get(param),i};
}
map.put(nums[i],i);
}
return null;
}
}
155. 最小栈 简单 栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
方法一:使用一个辅助栈存储最小值。
按照上面的思路,我们只需要设计一个数据结构,使得每个元素 a 与其相应的最小值 m 时刻保持一一对应。因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。
当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;
当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出;
在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。
class MinStack {
Deque<Integer> xStack;
Deque<Integer> minStack;
public MinStack() {
xStack = new LinkedList<Integer>();
minStack = new LinkedList<Integer>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int x) {
xStack.push(x);
minStack.push(Math.min(minStack.peek(), x));
}
public void pop() {
xStack.pop();
minStack.pop();
}
public int top() {
return xStack.peek();
}
public int getMin() {
return minStack.peek();
}
}
解法二:使用链表实现
class MinStack {
private Node head;
public void push(int x) {
if(head == null)
head = new Node(x, x);
else
head = new Node(x, Math.min(x, head.min), head);
}
public void pop() {
head = head.next;
}
public int top() {
return head.val;
}
public int getMin() {
return head.min;
}
private class Node {
int val;
int min;
Node next;
private Node(int val, int min) {
this(val, min, null);
}
private Node(int val, int min, Node next) {
this.val = val;
this.min = min;
this.next = next;
}
}
}
122. 买卖股票的最佳时机 II 简单 贪心算法
给定一个数组,它的第 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。
贪心算法,
一次遍历,只要今天价格小于明天价格就在今天买入然后明天卖出,时间复杂度O(n)
class Solution {
public int maxProfit(int[] prices) {
int ans=0;
for(int i=1;i<=prices.length-1;i++)
{
if(prices[i]>prices[i-1])
{
ans+=prices[i]-prices[i-1];
}
}
return ans;
}
}
排序
简单
242. 有效的字母异位词 排序
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
方法一:排序
算法:
通过将 ss 的字母重新排列成 tt 来生成变位词。因此,如果 TT 是 SS 的变位词,对两个字符串进行排序将产生两个相同的字符串。此外,如果 ss 和 tt 的长度不同,tt 不能是 ss 的变位词,我们可以提前返回。
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1);
Arrays.sort(str2);
return Arrays.equals(str1, str2);
}
方法二:哈希表
算法:
为了检查 tt 是否是 ss 的重新排列,我们可以计算两个字符串中每个字母的出现次数并进行比较。因为 SS 和 TT 都只包含 A-ZA−Z 的字母,所以一个简单的 26 位计数器表就足够了。
我们需要两个计数器数表进行比较吗?实际上不是,因为我们可以用一个计数器表计算 ss 字母的频率,用 tt 减少计数器表中的每个字母的计数器,然后检查计数器是否回到零。
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] counter = new int[26];
for (int i = 0; i < s.length(); i++) {
counter[s.charAt(i) - 'a']++;
counter[t.charAt(i) - 'a']--;
}
for (int count : counter) {
if (count != 0) {
return false;
}
}
return true;
}
350. 两个数组的交集 II 排序
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
方法一:哈希表
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return intersect(nums2, nums1);
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int num : nums1) {
int count = map.getOrDefault(num, 0) + 1;
map.put(num, count);
}
int[] intersection = new int[nums1.length];
int index = 0;
for (int num : nums2) {
int count = map.getOrDefault(num, 0);
if (count > 0) {
intersection[index++] = num;
count--;
if (count > 0) {
map.put(num, count);
} else {
map.remove(num);
}
}
}
return Arrays.copyOfRange(intersection, 0, index);
}
}
方法二:排序
如果两个数组是有序的,则可以便捷地计算两个数组的交集。
首先对两个数组进行排序,然后使用两个指针遍历两个数组。
初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int length1 = nums1.length, length2 = nums2.length;
int[] intersection = new int[Math.min(length1, length2)];
int index1 = 0, index2 = 0, index = 0;
while (index1 < length1 && index2 < length2) {
if (nums1[index1] < nums2[index2]) {
index1++;
} else if (nums1[index1] > nums2[index2]) {
index2++;
} else {
intersection[index] = nums1[index1];
index1++;
index2++;
index++;
}
}
return Arrays.copyOfRange(intersection, 0, index);
}
}
数组
简单
26. 删除排序数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
解题:
原地操作,所以用双指针
i 用来遍历数组
j 用来存储元素
i从1开始遍历数组,j从0开始存储元素
只要 nums[j] == nums[i],就增加 i ,不变j 跳过重复项,
直到遇到 nums[j] != nums[i] 时,把nums[i]的值复制到 nums[j+1] , 然后右移 j
循环 2 3 直到i到末尾
class Solution {
public int removeDuplicates(int[] nums) {
int j = 0;
for (int i = 1; i < nums.length; i++) {
if (nums[i] != nums[j]) {
j++;
nums[j] = nums[i];
}
}
return j + 1;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
思路 :动态规划
这道题用动态规划的思路并不难解决,比较难的是后文提出的用分治法求解,但由于其不是最优解法,所以先不列出来
动态规划的是首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans
如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字
如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字
每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果
时间复杂度:O(n)O(n)
class Solution {
public int maxSubArray(int[] nums) {
int ans = nums[0];
int sum = 0;
for(int num: nums) {
if(sum > 0) {
sum += num;
} else {
sum = num;
}
ans = Math.max(ans, sum);
}
return ans;
}
}
7. 整数反转 (简单)
class Solution {
public int reverse(int x) {
int rev = 0;
while (x != 0) {
int pop = x % 10;
x /= 10;
if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE / 10 && pop > 7)) return 0;
if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE / 10 && pop < -8)) return 0;
rev = rev * 10 + pop;
}
return rev;
}
}
13. 罗马数字转整数 简单
解题思路:一言蔽之,把一个小值放在大值的左边,就是做减法,否则为加法
class Solution {
public int romanToInt(String s) {
int sum = 0;
int preNum = getValue(s.charAt(0));
for(int i = 1;i < s.length(); i ++) {
int num = getValue(s.charAt(i));
if(preNum < num) {
sum -= preNum;
} else {
sum += preNum;
}
preNum = num;
}
sum += preNum;
return sum;
}
private int getValue(char ch) {
switch(ch) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return 0;
}
}
}
14. 最长公共前缀 简单
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
int length = strs[0].length();
int count = strs.length;
for (int i = 0; i < length; i++) {
char c = strs[0].charAt(i);
for (int j = 1; j < count; j++) {
if (i == strs[j].length() || strs[j].charAt(i) != c) {
return strs[0].substring(0, i);
}
}
}
return strs[0];
}
}
28. 实现 strStr()
class Solution {
public int strStr(String haystack, String needle) {
int L = needle.length(), n = haystack.length();
for (int start = 0; start < n - L + 1; ++start) {
if (haystack.substring(start, start + L).equals(needle)) {
return start;
}
}
return -1;
}
}
66. 加一 简单
class Solution {
public int[] plusOne(int[] digits) {
int sum =1;
for (int i = digits.length - 1; i >= 0; i--) {
sum = digits[i] + sum;
if(sum == 10) {
digits[i] = 0;
sum = 1;
} else{
digits[i] = sum;
return digits;
}
}
digits = new int[digits.length + 1];
digits[0] = 1;
return digits;
}
}
101对称二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return check(root, root);
}
public boolean check(TreeNode u, TreeNode v) {
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(u);
q.offer(v);
while (!q.isEmpty()) {
u = q.poll();
v = q.poll();
if (u == null && v == null) {
continue;
}
if ((u == null || v == null) || (u.val != v.val)) {
return false;
}
q.offer(u.left);
q.offer(v.right);
q.offer(u.right);
q.offer(v.left);
}
return true;
}
}
104. 二叉树的最大深度
/**
* Definition for a Nbinary tree node.
* public class Treeode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
} else {
int leftHeight = maxDepth(root.left);
int rightHeight = maxDepth(root.right);
return Math.max(leftHeight, rightHeight) + 1;
}
}
}
136. 只出现一次的数字
解法1 自己做法
使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则加1,最后再遍历集合判断value为1输出
class Solution {
public int singleNumber(int[] nums) {
Map<Integer, Integer> numberMap = new HashMap<>();
for (int number : nums) {
if (numberMap.containsKey(number)) {
int count = numberMap.get(number).intValue();
count++;
numberMap.put(number, count);
} else {
numberMap.put(number, 1);
}
}
int number = 0;
for (Map.Entry<Integer, Integer> entry : numberMap.entrySet()) {
if (entry.getValue() == 1) {
number = entry.getKey();
break;
}
}
return number;
}
}
解法2
答案是使用位运算。对于这道题,可使用异或运算 \oplus⊕。异或运算有以下三个性质。
任何数和 00 做异或运算,结果仍然是原来的数,即 a \oplus 0=aa⊕0=a。
任何数和其自身做异或运算,结果是 00,即 a \oplus a=0a⊕a=0。
class Solution {
public int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
}
160. 相交链表 简单
自己做法: 暴力法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode HeadTempA = headA;
ListNode HeadTempB = headB;
while(HeadTempB != null) {
HeadTempA = headA ;
while(HeadTempA != null) {
if(HeadTempA == HeadTempB) {return HeadTempA;}
HeadTempA = HeadTempA.next;
}
HeadTempB = HeadTempB.next;
}
return null;
}
}
方法3初看很难理解,但是细想就会发现很简单很巧妙 A和B两个链表长度可能不同,但是A+B和B+A的长度是相同的,所以遍历A+B和遍历B+A一定是同时结束。 如果A,B相交的话A和B有一段尾巴是相同的,所以两个遍历的指针一定会同时到达交点 如果A,B不相交的话两个指针就会同时到达A+B(B+A)的尾节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode HeadTempA = headA;
ListNode HeadTempB = headB;
while (HeadTempA != HeadTempB) {
if(HeadTempA != null) {
HeadTempA = HeadTempA.next;
} else {
HeadTempA = headB;
}
if(HeadTempB != null) {
HeadTempB = HeadTempB.next;
} else {
HeadTempB = headA;
}
}
return HeadTempA;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
237. 删除链表中的节点
与下一个节点交换
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
15. 三数之和 中等
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
}
}
2. 两数相加 中等
自己计算出来的
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int sum = l1.val + l2.val;
ListNode l3 = new ListNode();
ListNode header = l3;
header.val = sum % 10;
int t = sum / 10;
l1 = l1.next;
l2 = l2.next;
//对连个链表相同长度相加
while (l1 != null && l2 != null) {
int i1 = l1 != null ? l1.val : 0;
int i2 = l2 != null ? l2.val : 0;
sum = i1 + i2 + t;
t = sum / 10;
l3.next = new ListNode(sum % 10);
l3 = l3.next;
l1 = l1.next;
l2 = l2.next;
}
//对比另一个链表长的步份计算
while (l1 != null) {
sum = l1.val + t;
l3.next = new ListNode(sum % 10);
t = sum / 10;
l3 = l3.next;
l1 = l1.next;
}
//对比另一个链表长的步份计算
while (l2 != null) {
sum = l2.val + t;
l3.next = new ListNode(sum % 10);
t = sum / 10;
l3 = l3.next;
l2 = l2.next;
}
//t>0,需要放入最高位
if(t>0) { l3.next = new ListNode(t);}
return header;
}
}
3. 无重复字符的最长子串 中等
暴力
public static int lengthOfLongestSubstring(String s) {
if(s.isEmpty()) return 0;
char[] arr = s.toCharArray();
int length = 1;
Set<Character> strings = new HashSet<>();
for(int i=0; i<s.length();i++){
int lengthTemp = 1;
strings.clear();
strings.add(arr[i]);
for(int j=i; j<s.length()-1;j++){
if(strings.add(arr[j+1])) {
lengthTemp++;
} else {
break;
}
}
length = Math.max(length, lengthTemp);
}
return length;
}
class Solution {
public int lengthOfLongestSubstring(String s) {
// 哈希集合,记录每个字符是否出现过
Set<Character> occ = new HashSet<Character>();
int n = s.length();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = -1, ans = 0;
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.remove(s.charAt(i - 1));
}
while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
// 不断地移动右指针
occ.add(s.charAt(rk + 1));
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = Math.max(ans, rk - i + 1);
}
return ans;
}
}
120 三角形最短路径和(动态规划)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
//对于一个数字的处理
if (triangle.size() == 1) return triangle.get(0).get(0);
int min = Integer.MAX_VALUE;
int rec[][] = new int[triangle.size()][];
for (int i = 0; i < triangle.size(); ++i){
rec[i] = new int[i + 1];
}
rec[0][0] = triangle.get(0).get(0);
for (int i = 0; i < triangle.size() - 1; ++i){
List<Integer> curLine = triangle.get(i);
List<Integer> nextLine = triangle.get(i + 1);
for (int j = 0; j < triangle.get(i).size(); ++j){
int first = nextLine.get(j);
int second = nextLine.get(j + 1);
int cur = rec[i][j];
/*当为第一个的时候应该是直接相加即可其余的情况进行最小值的判断因为到达这个位置存在着两条路径需要对这两条路径进行比较*/
if (j == 0){
//j == 0的情况
rec[i + 1][j] = first + cur;
rec[i + 1][j + 1] = second + cur;
}else {
//j > 1的情况需要对之前得到的值进行比较
rec[i + 1][j] = Math.min(rec[i + 1][j], cur + first);
//对于第二个位置直接相加因为在下一次的时候这个位置会转为第一个位置会进行最小值的比较在纸上画一下图是很好理解的
rec[i + 1][j + 1] = second + cur;
}
}
}
for (int i = 0; i < triangle.size(); ++i){
min = Math.min(min, rec[triangle.size() - 1][i]);
}
return min;
}
}
80. 删除有序数组中的重复项 II (中等)
因为给定数组是有序的,所以相同元素必然连续。我们可以使用双指针解决本题,遍历数组检查每一个元素是否应该被保留,如果应该被保留,就将其移动到指定位置。具体地,我们定义两个指针 \textit{slow}slow 和 \textit{fast}fast 分别为慢指针和快指针,其中慢指针表示处理出的数组的长度,快指针表示已经检查过的数组的长度,即 \textit{nums}[\textit{fast}]nums[fast] 表示待检查的第一个元素,\textit{nums}[\textit{slow} - 1]nums[slow−1] 为上一个应该被保留的元素所移动到的指定位置。
因为本题要求相同元素最多出现两次而非一次,所以我们需要检查上上个应该被保留的元素 \textit{nums}[\textit{slow} - 2]nums[slow−2] 是否和当前待检查元素 \textit{nums}[\textit{fast}]nums[fast] 相同。当且仅当 \textit{nums}[\textit{slow} - 2] = \textit{nums}[\textit{fast}]nums[slow−2]=nums[fast] 时,当前待检查元素 \textit{nums}[\textit{fast}]nums[fast] 不应该被保留(因为此时必然有 \textit{nums}[\textit{slow} - 2] = nums[\textit{slow} - 1] = \textit{nums}[\textit{fast}]nums[slow−2]=nums[slow−1]=nums[fast])。最后,\textit{slow}slow 即为处理好的数组的长度。
特别地,数组的前两个数必然可以被保留,因此对于长度不超过 22 的数组,我们无需进行任何处理,对于长度超过 22 的数组,我们直接将双指针的初始值设为 22 即可。
class Solution {
public int removeDuplicates(int[] nums) {
int n = nums.length;
if (n <= 2) {
return n;
}
int slow = 2, fast = 2;
while (fast < n) {
if (nums[slow - 2] != nums[fast]) {
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
}
}
83. 删除排序链表中的重复元素 简单
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode cur = head;
while (cur.next != null) {
if (cur.val == cur.next.val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
}
86. 分隔链表 中等
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode small = new ListNode(0);
ListNode smallHead = small;
ListNode large = new ListNode(0);
ListNode largeHead = large;
while (head != null) {
if (head.val < x) {
small.next = head;
small = small.next;
} else {
large.next = head;
large = large.next;
}
head = head.next;
}
large.next = null;
small.next = largeHead.next;
return smallHead.next;
}
}