11.给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1 和 0。
整体思路是将两个字符串较短的用 0 补齐,使得两个字符串长度一致,然后从末尾进行遍历计算,得到最终结果。
但由于字符串操作原因,不确定最后的结果是否会多出一位进位,所以会有 2 种处理方式:
第一种,在进行计算时直接拼接字符串,会得到一个反向字符,需要最后再进行翻转
第二种,按照位置给结果字符赋值,最后如果有进位,则在前方进行字符串拼接添加进位
时间复杂度:O(n)
package leetcode;
public class AddBinary {
public static String addBinary(String a, String b) {
StringBuilder result = new StringBuilder();
int ca=0;
for(int i=a.length()-1,j=b.length()-1;i>=0||j>=0;i--,j--) {
int sum=0;
sum+=(i>=0)?a.charAt(i)-'0':0;
sum+=(j>=0)?b.charAt(j)-'0':0;
sum+=ca;
result.append(sum%2);
ca=sum/2;
}
result.append(ca==1?ca:"");
return result.reverse().toString();
}
public static void main(String[] args) {
System.out.println(addBinary("101","110"));
}
}
12.给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
排列组合
从根节点到空节点一共有多少条路径
package wg;
import java.util.ArrayList;
import java.util.List;
//这里不用StringBuffer是因为它不产生新的未使用对象
public class Solution {
private static String[] letterMap = {
" ", //0
"", //1
"abc", //2
"def", //3
"ghi", //4
"jkl", //5
"mno", //6
"pqrs", //7
"tuv", //8
"wxyz" //9
};
private static ArrayList res;//用来装递归过程中找到的结果
//得到结果集
public static List letterCombinations(String digits) {
res = new ArrayList();
if(digits.equals(""))
return res;
findCombination(digits, 0, "");
return res;
}
//递归函数 处理得字符串 字符的位置 得到的结果s
private static void findCombination(String digits, int index, String s){
if(index == digits.length()){
res.add(s);
return;
}
Character c = digits.charAt(index);
String letters = letterMap[c - '0'];
for(int i = 0 ; i < letters.length() ; i ++){
findCombination(digits, index+1, s+letters.charAt(i));
}
return;
}
public static void main(String[] args) {
List results = new ArrayList<>();
results = letterCombinations("235");
for(String s : results)
System.out.println(s);
}
}
/*
adj
adk
adl
aej
aek
ael
afj
afk
afl
bdj
bdk
bdl
bej
bek
bel
bfj
bfk
bfl
cdj
cdk
cdl
cej
cek
cel
cfj
cfk
cfl
*/
13.给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
整体思路是让前面的指针先移动n步,之后前后指针共同移动直到前面的指针到尾部为止
1.设预先指针 pre 的下一个节点指向 head,设前指针为 start,后指针为 end,二者都等于 pre
2.start 先向前移动n步
3.之后 start 和 end 共同向前移动,此时二者的距离为 n,当 start 到尾部最后一个节点时(start.next != null
),end 的下一个节点位置恰好为倒数第 n 个节点
4.循环结束条件为 start.next != null
5.删除后返回 pre.next,为什么不直接返回 head 呢,因为 head 有可能是被删掉的点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode start = pre, end = pre;
while(n != 0) {
start = start.next;
n--;
}
while(start.next != null) {
start = start.next;
end = end.next;
}
end.next = end.next.next;
return pre.next;
}
}
14.给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空;
1.可以增加一个判断:如果栈的深度大于字符串长度的1/2,就返回false。因为当出现这种情况的时候,即使后面的全部匹配,栈也不会为空。
2.如果字符串长度是奇数,直接返回false。
package wg;
import java.util.HashMap;
import java.util.Stack;
public class Solution {
private HashMap mappings;
public Solution() {
this.mappings = new HashMap<>();
//把结束符号用来判断
this.mappings.put(')','(');
this.mappings.put(']','[');
this.mappings.put('}','{');
}
public boolean isValid(String s){
if(s.length()%2==1)
return false;
Stack stack = new Stack<>();
for(int i=0,j=s.length();ij/2){
if(stack.size()>j/2)return false;
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.isValid("{}(())[[]]"));
}
}
15.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
示例
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
递归实现
新链表也不需要构造新节点,我们下面列举递归三个要素
1.终止条件:两条链表分别名为l1
和l2
,当l1
为空或l2
为空时结束
2.返回值:每一层调用都返回排序好的链表头
3.本级递归内容:如果l1
的val
值更小,则将l1.next
与排序好的链表头相接,l2
同理
复杂度:O(m+n)
,m
为l1
的长度,n
为l2
的长度
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) {
return l2;
}
if(l2 == null) {
return l1;
}
//l1的下一个节点连接谁?
if(l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {//l2的下一个节点连接谁?
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
16.给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
什么是动态规划?在此题中,动态规划的思想类似于数学归纳法,当知道所有
i
的情况时,我们可以通过某种算法算出 i=n
的情况。
本题最核心的思想是,考虑i=n
时相比n-1
组括号增加的那一组括号的位置。
思路:
当我们清楚所有i
时括号的可能生成排列后,对与 i=n
的情况,我们考虑整个括号排列中最左边的括号。
它一定是一个左括号,那么它可以和它对应的右括号组成一组完整的括号 "( )",我们认为这一组是相比n-1
增加进来的括号。剩下的括号要么在这一组新增的括号内部,要么在这一组新增括号的外部(右侧)。
所以:
剩下的N-1个括号分为两部分,P个括号和Q个括号,P+Q=N-1,然后这两部分分别处于新增的括号和新增的括号右边,各自进行括号的排列组合。
package wg;
import java.util.LinkedList;
import java.util.List;
public class Solution {
public static List generateParenthesis(int n){
LinkedList> results = new LinkedList>();
if(n==0)
return results.get(0);
LinkedList list0 = new LinkedList<>();
list0.add("");
results.add(list0);//results.get(0)的结果;
LinkedList list1 = new LinkedList<>();
list1.add("()");
results.add(list1);//results.get(1)的结果;
//分别得到results.get(i)的结果
for(int i=2;i<=n;i++){
LinkedList temp = new LinkedList<>();
//p+q=i-1 这里遍历p
for(int j=0;j str1 = results.get(j);
LinkedList str2 = results.get(i-1-j);
for(String s1:str1){
for(String s2:str2){
String el = "("+s1+")"+s2;//p位于内部,q位于右边
temp.add(el);
}
}
}
results.add(temp);
}
return results.get(n);
}
public static void main(String[] args) {
List re = generateParenthesis(4);
System.out.println(re);
}
}
[()()()(), ()()(()), ()(())(), ()(()()), ()((())), (())()(), (())(()), (()())(), ((()))(), (()()()), (()(())), ((())()), ((()())), (((())))]
17.实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
我们需要从右边找到第一对两个连续的数字 a[i]
和 a[i−1]
,它们满足 a[i]>a[i-1]
。现在,没有对 a[i-1]
右侧的重新排列可以创建更大的排列,因为该子数组由数字按降序组成。因此,我们需要重新排列a[i−1]
右边的数字,包括它自己。
现在,什么样子的重新排列将产生下一个更大的数字呢?我们想要创建比当前更大的排列。因此,我们需要将数字 a[i−1]
替换为位于其右侧区域的数字中比它稍大(就是大于但是这个是大于中的最小)的数字,如 下图的a[j]
。
我们交换数字
a[i−1]
和a[j]
。我们现在在索引i−1
处有正确的数字。 但目前的排列仍然不是我们正在寻找的排列。我们需要通过仅使用 a[i−1]
右边的数字来形成最小的排列。 因此,我们需要重新放置右边的数字以获得最小的排列。右边的所有数字都已按降序排序,我们只需要反转 a[i−1]
之后的数字,以获得下一个最小的字典排列。
下面的动画将有助于你理解:
package wg;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class Solution {
private static void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private static void reverse(int[] nums, int start) {
int i = start, j = nums.length - 1;
while (i < j) {
swap(nums, i, j);
i++;
j--;
}
}
public static void nextPermutation(int[] nums) {
int i = nums.length - 2;
//从倒是第2个开始找 当nums[i+1]>nums[i]退出
while (i >= 0 && nums[i + 1] <= nums[i]) {
i--;
}
//i<0表示整个是降序排列,反转整个数组满足题目条件
if (i >= 0) {
int j = nums.length - 1;
//找打稍大的那个数然后交换位置
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
//这个反转开始的地方是i的下一个位置到最后
reverse(nums, i + 1);
}
public static void main(String[] args) {
int[] nums = {3,2,4,1};
nextPermutation(nums);
System.out.println(Arrays.toString(nums));
//[3, 4, 1, 2]
}
}
18.给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
我们利用两个计数器
left
和right
。首先,我们从左到右遍历字符串,对于遇到的每个(
,我们增加left
计算器,对于遇到的每个)
,我们增加right
计数器。每当left
计数器与right
计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串。如果right
计数器比left
计数器大时,我们将left
和right
计数器同时变回 0 。
接下来,我们从右到左做一遍类似的工作。
public int longestValidParentheses(String s) {
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * right);
} else if (right >= left) {
left = right = 0;
}
}
left = right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * left);
} else if (left >= right) {
left = right = 0;
}
}
return maxlength;
}
19.给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置
你的算法时间复杂度必须是 O(log n)
级别。
如果数组中不存在目标值,返回[-1, -1]
。
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
二分查找算法
package wg;
import java.util.Arrays;
public class Solution {
public int[] searchRange(int[] nums, int target) {
int len = nums.length;
// 特判,这一步很重要,否则执行到后序方法可能会出现数组下标越界
// 同时后序两个方法也不用做特殊判断了
if (len == 0) {
return new int[]{-1, -1};
}
int num1 = findLowerBound(nums, target);
// 细节:如果左边界都搜索不到,右边界也没有必要看了
if (num1 == -1) {
return new int[]{-1, -1};
}
int num2 = findUpBound(nums, target);
return new int[]{num1, num2};
}
private int findLowerBound(int[] nums, int target) {
int len = nums.length;
int left = 0;
int right = len;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid;
} else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid;
}
}
// 因为有可能不存在目标元素,最后一定要单独判断一下
if(left==nums.length)return -1;
return nums[left] == target ? left : -1;
}
private int findUpBound(int[] nums, int target) {
// 找等于 target 的最后 1 个数的索引,大于一定不符合要求
// 因为有可能不存在,最后一定要单独判断一下
int len = nums.length;
int left = 0;
int right = len;
while (left < right) {
int mid = left + (right - left ) / 2;
if (nums[mid] > target) {
right = mid;
} else if(nums[mid] == target){
left = mid+1;
}else{
left = mid+1;
}
}
// 因为有可能不存在目标元素,最后一定要单独判断一下
if (left == 0) return -1;
return nums[left-1] == target ? (left-1) : -1;
}
public static void main(String[] args) {
int[] nums = {5, 7, 7, 8, 8, 10};
int target = 8;
Solution solution4 = new Solution();
int[] res = solution4.searchRange(nums, target);
System.out.println(Arrays.toString(res));
}
}
20.给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
说明:
candidates 中的数字可以无限制重复被选取。
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
回溯算法 + 剪枝
思路:以target = 7
为根结点,每一个分支做减法。减到0
或者负数
的时候,剪枝。其中,减到0
的时候结算,这里 “结算” 的意思是添加到结果集。
这张图画出的结果有4
个0
,对应的路径是[[2, 2, 3], [2, 3, 2], [3, 2, 2], [7]]
,而示例中的解集只有[[7], [2, 2, 3]]
,很显然,结果集出现了重复。重复的原因是
后面分支的更深层的边出现了前面分支低层的边的值。
这个问题也不难解决,把候选数组排个序就好了,后面选取的数不能比前面选的数还要小
package wg;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
public class Solution {
private List> res = new ArrayList<>();
private int[] candidates;
private int len;
public List> combinationSum(int[] candidates, int target) {
int len = candidates.length;
if (len == 0) {
return res;
}
// 优化添加的代码1:先对数组排序,可以提前终止判断
Arrays.sort(candidates);
this.len = len;
this.candidates = candidates;
findCombinationSum(target, 0, new Stack<>());
return res;
}
//第一个参数 此时剩余组合的值 查找到了数组的第几个数字 保存结果的栈
private void findCombinationSum(int residue, int start, Stack pre) {
if (residue == 0) {
// Java 中可变对象是引用传递,因此需要将当前 path 里的值拷贝出来
//把栈变成ArrayList 利用栈变成构造ArrayList
res.add(new ArrayList<>(pre));
return;
}
// 优化添加的代码2:在循环的时候做判断,尽量避免系统栈的深度
// residue - candidates[i] 表示下一轮的剩余,如果下一轮的剩余都小于 0 ,就没有必要进行后面的循环了
// 这一点基于原始数组是排序数组的前提,因为如果计算后面的剩余,只会越来越小
for (int i = start; i < len && residue - candidates[i] >= 0; i++) {
pre.add(candidates[i]);
// 【关键】因为元素可以重复使用,这里递归传递下去的是 i 而不是 i + 1
findCombinationSum(residue - candidates[i], i, pre);
pre.pop();
}
}
public static void main(String[] args) {
int[] candidates = {2, 3, 6, 7};
int target = 7;
Solution solution = new Solution();
List> combinationSum = solution.combinationSum(candidates, target);
System.out.println(combinationSum);
}
}
文章参考
https://leetcode-cn.com/problemset/hot-100/