16. 最接近的三数之和
思路:和15题一样,更新指针前做一个sub和sum的更新判断
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int sub = Integer.MAX_VALUE;
int sum = 0;
//固定第一个指针,剩下两个数用左右指针找
for (int k = 0; k < nums.length - 2; k++) {
//跳过重复的,减少不必要的判断
if (k > 0 && nums[k] == nums[k - 1]) continue;
//定义双指针
int i = k + 1;
int j = nums.length - 1;
while (i < j) {
if (Math.abs(nums[k] + nums[i] + nums[j] - target) < sub) {
sum = nums[k] + nums[i] + nums[j];
sub = Math.abs(sum - target);
}
if (nums[k] + nums[i] + nums[j] > target) {
while (i < j && nums[j] == nums[--j]) ;
} else {
while (i < j && nums[i] == nums[++i]) ;
}
}
}
return sum;
}
17. 电话号码的字母组合(常考)
回溯
思路:
- 本题使用回溯来解决,回溯是深度优先搜索的一种,实际上是一个决策树遍历的过程
- 回溯具有三个需要思考的问题:
- 当前已经走过的路径
- 可选的路径
- 结束条件
- 核心问题:
- 递归之前做选择,递归之后撤销选择
- 用一个数组来存储映射关系
List<String> res = new ArrayList<>();
String[] map = {" ", "*", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return res;
}
traceback(digits, 0, new StringBuilder());
return res;
}
public void traceback(String s, int depth, StringBuilder path) {
//base case,depth = s.length() - 1的时候所有字符都翻译好了,depth = s.length()直接加就行了
if (depth == s.length()) {
res.add(path.toString());
return;
}
char temp = s.charAt(depth);
String str = map[temp - '0'];
for (int i = 0; i < str.length(); i++) {//对于例如"mno"每一个字符去选择
path.append(str.charAt(i));//做出选择
traceback(s, depth + 1, path);//递归下去
path.deleteCharAt(path.length() - 1);//撤销选择
}
}
迭代
用一个队列来实现字符串的拼接,有点类似于树的层序遍历
- 第一个for循环是控制有多少个数字(也就是字符串的长度)
- 第一个while循环是查看队首元素,也就是上一轮拼接好的数字
- 第二个for循环是将上一轮拼接好的数字每一个后面都接着拼接一个本次要添加的char
举个例子
- 第 1 次 for 循环(i=0)结束后变为 a, b, c;
- 第 2 次 for 循环(i=1)的第 1 次 while 循环 a 出队,分别加上 d e f 然后入队,就变成 b c ad ae af
- 第 2 次 for 循环(i=1)的第 2 次 while 循环 b 出队,分别加上 d e f 然后入队,就变成 c ad ae af bd be bf
- 第 2 次 for 循环的第 3 次 while 循环 c 出队,分别加上 d e f 然后入队,就变成 ad ae af bd be bf cd ce cf
是不是很像BFS进行层序遍历
public List<String> letterCombinations2(String digits) {
LinkedList<String> ans = new LinkedList<String>();
if(digits.isEmpty()) return ans;
String[] mapping =
new String[] {"0", "1", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
ans.add("");
for(int i =0; i<digits.length();i++){
int x = Character.getNumericValue(digits.charAt(i));
while(ans.peek().length()==i){ //查看队首元素
String t = ans.remove(); //队首元素出队
for(char s : mapping[x].toCharArray())
ans.add(t+s);
}
}
return ans;
}
18. 四数之和
和15题是一样的,用两个for循环固定两个指针,剩下两个用双指针去找,时间复杂度立方级别
public static List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int j = 0; j < nums.length - 3; j++) {
if (j > 0 && nums[j] == nums[j - 1]) continue;//去重
for (int i = j + 1; i < nums.length - 2; i++) {
if (i > j + 1 && nums[i] == nums[i - 1]) continue;//去重
int lo = i + 1;
int hi = nums.length - 1;
int sum = target - nums[i] - nums[j];
while (lo < hi) {
if (nums[lo] + nums[hi] == sum) {
res.add(Arrays.asList(nums[j], nums[i], nums[lo], nums[hi]));
while (lo < hi && nums[lo] == nums[++lo]) ;
while (lo < hi && nums[hi] == nums[--hi]) ;
} else if (nums[lo] + nums[hi] < sum) {
lo++;
} else {
hi--;
}
}
}
}
return res;
}
19. 删除链表的倒数第N个节点
思路:
- 考虑可能删除头结点,设置一个哨兵
获取倒数第N+1的节点slow,然后slow.next=slow.next.next(fast要比slow多走一步N+1步)
public ListNode removeNthFromEnd(ListNode head, int n) {
//考虑头结点被删除的情况,所以要有哨兵
ListNode pre=new ListNode(-1);
pre.next=head;
//快慢指针,注意提前返回
ListNode fast=pre,slow=pre;
//fast走n+1是为了slow指向删除节点前一个节点
for(int i=1;i<=n+1;i++){
fast=fast.next;
}
while (fast!=null){
fast=fast.next;
slow=slow.next;
}
//删除slow节点
slow.next=slow.next.next;
return pre.next;
}
20. 有效的括号(常考)
思路:
- 典型的栈,利用后进先出的特性
- 遇到前括号就把对应的后括号放到栈里去,然后遇到后括号弹栈,判断是否相等,当然这个时候如果栈是空的话,就证明后括号的数量比前括号多,也是提前返回false的
public boolean isValid(String s) {
if(s.length()==0) return true;
if(s.length()%2==1) return false;
Stack<Character> stack = new Stack<>();
for(char c:s.toCharArray()){
if(c=='(')
stack.push(')');
else if(c=='{')
stack.push('}');
else if(c=='[')
stack.push(']');
else{
//直接右括号,或者不等,提前返回
if(stack.empty() || c!=stack.pop()) return false;
}
}
return stack.empty();
}