力扣1
1. 两数之和
解:
把相加的思想转化成有没有。查看某个元素有没有,用哈希表比遍历更好,因为哈希表中通过key找value是常数时间。
因此可以先把数组元素放到哈希表中,然后遍历一遍数组,每到一个元素时,就看看哈希表中是否存在另外的元素
创建数组:
int[] intArray2 = new int[]{20,21,22}
2. 两数相加
解:
把2个表头赋值给新指针作为保护,2个表只要有数就遍历下去,若遇到空,则假定为0
同时注意进位,若最后还有进位,则再加个节点。采用头结点的方式方便写代码,不过最后返回头接地.next就好了
利用三目运算符简化写法
直接在.next的时候创建新节点就可以了,不用单独创建出来
2的3次方:Math.pow(2,3)
3. 无重复字符的最长子串
解答:
子串不是子序列,子串是连续的。
字符类型的Character是Char的包装类
获得字符串长度:字符串.length()
HashSet只放单个元素,且无顺序,元素唯一
Set<String> s = new HashSet<>();
添加元素:s.add(元素)
删除元素:s.remove(元素)
是否含有元素:s.contains(元素)
查看是否含有某个元素可以也使用Set
字符串.charAt(索引数字):返回字符串中指定索引处的字符
Math.max(数字1,数字2):返回2个数中的最大值
思路:判断一个字符串里面是否含有重复字符,用Set,先看下一个元素是否在set中,若在,则重复,若不在,则加入,然后继续下一个;
用2个指针定好一个子串,注意当第一个字符查完后,左指针开始指向下一个字符,右指针不必动,因为由之前的结果可知之间夹的都是不同的字符。
4. 寻找两个正序数组的中位数
解:
将2个数组归并为一个有序数组,然后直接取数
5. 最长回文子串
解答:
动态规划:把子问题的答案保存到矩阵当中,以便下次用到
“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串
回文串满足:回文串1=a回文串2a(若回文串1长度>2)
数组长度为n,不超过数组尾巴就是索引<n
两指针定一个状态《==数学上==》二维矩阵或上下三角矩阵的第i行,第j列取什么值
找串问题对应到2指针问题,2指针问题对应到求上三角矩阵问题
其中i表示子串起始位置,j表示子串终止位置,值1表示回文串,值0反之,该矩阵中的值满足关系:
a[i][i] = 1; // 长度为1一定是回文串
a[i][i+1]=(s[i] == s[j]) // 长度为2,则要求2字母要相同才能是回文串
a[i][j] = (s[i]==s[j] && a[i+1][j-1]) // 长度大于2,要求首尾字母相同,且里面是回文串
字符串.substring(i,j):截取字符串的子串:索引从 i 到 j-1 !
可以先定好矩阵再找回文串;也可以当一个长度试完,就定好一个回文串,然后试下一个长度,再改变回文串,如此而已就能找到最长的回文串。
6. 正则表达式匹配
解答:
idea:Ctrl+R:字符替换
a*匹配:空串,a,aa,aaa,aaaa....
把s串与p串的匹配《==》s的前s.length()是否匹配p的前p.length,进而转化为二指针问题(动态规划)
由于s和p有可能是空串,因此为了兼容一切,给s和p前面附加空串:s=空串+s,p=空串+p
以下的s和p的第0个字符都是空串
R[i][j] : s 的前 i 个字符是否和 p 的前 j 字符匹配
所以先按照方程构造矩阵,然后结果返回R[ s.length-1 ][ p.length-1 ]就是答案
方程:
边界条件:
R[0][0]=True; // 空串和空串是匹配的
i>0 : R[ i ][0]=False // 有字母的肯定和空串不匹配
j>0 : R[ 0 ][ j ] = p[ j ] =='*' && R[ 0 ][ j-2 ] // 如果p[j]不是*的话,就要求s[0]首先是个字母,则空串肯定不匹配,若是*, 那么如果空串和p[j-2]是匹配的,则*的作用就是匹配前一个字符0次,因此结果=1
i>0, j>0 :
如果p[ j ] != *:R[ i ][ j ] =( p[ j ]==‘.’ || s[ i ] == p[ j ] ) && R[ i-1 ][ j-1 ] // 此时要求p[ j ] 要和s[ i ]匹配,且s的前 i-1 也要和p的前 j-1匹配
如果p[ j ]== *,且 j>1 :R[ i ][ j ] = R[ i ][ j-2 ] || ( ( s[ i ] ==p[ j-1 ] || p[ j-1 ]=='.' ) && R[ i-1 ][ j ] )
解释:
为了达到递推关系,需要考虑和小的关系才能往后递推,讨论*发挥的作用:
如果*匹配0次,则要求s的前 i 要和p的前 j-2 匹配;
如果*匹配1次以上,则s的前 i-1和p的前 j 必匹配,即 R[ i-1 ][ j ]必为1,此时合格串只可能是s的前 i-1 项 加上一堆p[ j-1 ]或者是p[ j-1 ]=. ,这样就构成了充要条件,由于要合法,则要求 j >1
R[ i ][ i ] = False // 其他情况都是false
图例:
左边一列是s串,上面一行是p串
7.盛最多水的容器
解答:
只要确定2个柱子就行了,不用管中间的柱子是否高于或低于水面
水的面积 = 长*宽 = min(h左柱子,h右柱子) * (x右-x左)
方法1:
暴力破解:试出全部的面积,找最大的
方法2:
双指针法:2个指针向中间移动:左指针指向最左边柱子,右指针指向最右边柱子,这样订好了2个柱子后,算面积;
(若左柱子高>右柱子高,如果把左指针向右移动一格,则宽变小,高不会增大,因此面积变小,所以只能移动右指针)
哪个指针指向的柱子矮,就移动哪个,然后算一下面积
每次算完面积都要比一下,最后面积最大值就是所求
8.三数之和
解:
Arrays.sort(a); 对a数组升序排序
Arrays.sort(a,startIndex,endIndex);对a中指定位置的小数组升序排序,【startIndex,endIndex)
sout:快速打出System.out.println()
List是有序,可重复的容器,类似数组:List<String> l = new ArrayList<>();有add,remove,contains方法
数组截取:System.arraycopy(arr2,0,arr1,0,3); // 从arr2的0索引开始,向arr1的0索引复制过去3个数
最终结果(a,b,c)可以看成是排好序的:a<=b<=c,因此可以先定下最小的来,则首先对数组从大到小排序,外层循环从左到右扫描,扫描一个定一个a
当定下a后,就在a的后面几个数字中找b,c,使得b+c=-a,此时问题成为了2sum问题。
左指针指向最小,右指针指向最大,2个指针向中间移动直到快碰面。每定好一个位置,则算一下b+c,若=-a,则保存到 l 中,然后移动至不重复的元素
若>-a,则移动右边,若<,则移动左边
解:
public List<String> letterCombinations(String digits) { List<String> re = new ArrayList<>(); Map<Character,String> map = new HashMap<>(); map.put('2',"abc"); map.put('3',"def"); map.put('4',"ghi"); map.put('5',"jkl"); map.put('6',"mno"); map.put('7',"pqrs"); map.put('8',"tuv"); map.put('9',"wxyz"); // 若digits不是空串 if(digits.length()>0){ traceBack(re,map,digits,0,new StringBuffer()); // 当前答案是空串,要找后面的,就要从index=——1+1=0处开始 } return re; } public void traceBack(List<String> re,Map<Character,String> map,String digits,int index,StringBuffer curr){ if(curr.length()==digits.length()){ re.add(curr.toString()); return; } String t = map.get(digits.charAt(index)); // 遍历当前答案, for(int i=0;i<t.length();i++){ curr.append(t.charAt(i)); // 基于当前答案,收集后面所有的答案 traceBack(re,map,digits,index+1,curr); curr.deleteCharAt(index); } }
本题有限的对应关系可以以key,value对存储于哈希表中
java中的对象(比如new出来的)是按照引用传递的,因此对象传递给函数时,函数内部做出改变也会影响原来的值,操作的是同一个东西
StringBuffer:也是字符串,只不过能给它添加和删除字符:
.append("在尾部添加的字符串")
deleteCharAt(索引):删除字符串中某个索引对应的字符;toString():转成普通字符串
delete(int a,int b)有两个参数,使用时删除索引从a开始(包含a)到b(不包含b)的所有字符
回溯(采用循环递归的方法,注意要写出口):
以当前构造的答案为基础,然后找出所有符合题意的答案并放到一个集合中;
再构造一个答案,然后...;直到遍历构造完当前所有答案。此时就得到所有满足题意的答案
(本题:以当前正在拼的字符串为基础,然后找出从digits的索引index到结束的所有符合题意的字符串,并放到combinations中;
再构造一个字符串,然后...,直到遍历完当前所有可能的字符串)
10 删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
解答:
public ListNode removeNthFromEnd(ListNode head, int n) { ListNode header = new ListNode(); header.next=head; ListNode p = head; int length=0; while (p!=null){ length++; p=p.next; } int count = length-n; p=header; for(int i=0;i<count;i++){ p=p.next; } p.next = p.next.next; return header.next; }
为了统一化操作,新增一个头结点。
先求出链表长度,再-n,结果值为从首元节点开始计量的,移动指针就定位到了要删的那个元素的前一个。然后p.next=p.next.next;
11有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()" 输出: true
示例 2:
输入: "()[]{}" 输出: true
示例 3:
输入: "(]" 输出: false
示例 4:
输入: "([)]" 输出: false
示例 5:
输入: "{[]}" 输出: true
解:
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(int i=0;i<s.length();i++){
if(s.charAt(i)=='(' || s.charAt(i)=='[' || s.charAt(i)=='{' ){
stack.push(s.charAt(i));
}else {
char t = stack.empty()?' ':stack.pop();
if(s.charAt(i)==']'){
if(t!='['){
return false;
}
}else if(s.charAt(i)==')'){
if(t!='('){
return false;
}
}else {
if(t!='{'){
return false;
}
}
}
}
return stack.empty();
}
栈:Stack<Character> stack = new Stack<>();
从左到右扫描字符串,若遇到左括号则压栈,若遇到右括号,如),则弹栈,若弹出来的是(,则继续扫描,否则直接返回false
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
解:
方法一:
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 增加头结点便于操作
ListNode re = new ListNode(0);
ListNode p =re;
while (l1!=null && l2!=null){
if(l1.val<l2.val){
p.next = new ListNode(l1.val);
p=p.next;
l1=l1.next;
}else {
p.next = new ListNode(l2.val);
p=p.next;
l2=l2.next;
}
}
if (l1!=null){
p.next = l1;
}
if(l2!=null){
p.next=l2;
}
return re.next;
}
2个指针分别指向2个链表,对于小的则新建一个节点并拷贝,然后移动该指针继续比较
方法二:
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null){
return l2;
}
if(l2==null){
return l1;
}
if(l1.val<l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else {
l2.next= mergeTwoLists(l1,l2.next);
return l2;
}
}
该问题等价于给定2个表头节点,返回以这两个节点开头的2链表的合并
进而写出递归函数:给定2个节点,返回以这两个节点开头的2链表的合并
此时该问题成为递归中的特例
13 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3 输出:[ "((()))", "(()())", "(())()", "()(())", "()()()" ]
解:
public List<String> generateParenthesis(int n) {
List<String> re= new ArrayList<>();
re = trace(n); // 循环递归实现回溯
return re;
}
private List<String> trace(int n) {
List<String> re= new ArrayList<>();
if(n==0){
re.add("");
return re;
}
// curr代表当前收集到的结果
StringBuffer curr =new StringBuffer();
// ( f(i) ) f(n-1-i)
for(int i=0;i<n;i++){
// 固定 i
for(String temp:trace(i)){
curr.append("(");
curr.append(temp);
curr.append(")");
for(String last:trace(n-1-i)){
curr.append(last);
re.add(curr.toString());
curr.delete(curr.length()-last.length(),curr.length());
}
curr = new StringBuffer();
}
}
return re;
}
字符数组arr到字符串:new String(arr)
要取ArrayList里面的的第i个元素:get(i)
for(元素:集合):遍历该集合里面的每个元素,有几个元素循环几次
思路:合法字符串中,第一个一定是(,后面一定有一个)和它匹配,在这对括号里面的字符串必然合法,右边的字符串也必然合法
括号里面有i对括号,括号右边有j对括号,且i+j=n,遍历所有可能的i,j就是结果。
设f(n):基于n对括号的集合,f(n) = ( 与 f(i) 与 ) 与 f(j) 做笛卡尔乘积的结果,这些结果的并集(i+j=n-1)
14 合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6
示例 2:
输入:lists = [] 输出:[]
示例 3:
输入:lists = [[]] 输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i]
按 升序 排列lists[i].length
的总和不超过10^4
解答:
public ListNode mergeKLists(ListNode[] lists) {
// 自定义一个头结点
ListNode re = new ListNode(0);
for(int i=0;i<lists.length;i++){
re.next = merge(re.next,lists[i]);
}
return re.next;
}
// 实现2链表合并
public ListNode merge(ListNode p,ListNode list){
if(p==null){
return list;
}
if(list==null){
return p;
}
if(p.val<list.val){
p.next = merge(p.next,list);
return p;
}else{
list.next = merge(p,list.next);
return list;
}
}
new一个新的链表,拿着它和每一个链表做两两合并,合并n次就好了
15 下一个排列
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。1,2,3
→ 1,3,2
3,2,1
→ 1,2,3
1,1,5
→ 1,5,1
解:
public void nextPermutation(int[] nums) {
int i = 0; // 为了指向132651中的2
int j = 0; // 为了指向132651中的5
for(i=nums.length-1;i>=0;i--){
// 若本身就是倒序,则原方法会出错,此时直接重排,然后返回
if(i-1<0){
Arrays.sort(nums);
return;
}
if(nums[i-1]<nums[i]){
break;
}
}
i--;
j=i+1;
for(int k=j;k<nums.length;k++){
if(nums[k]>nums[i]){
j=k;
}else {
break;
}
}
swap(nums,i,j);
Arrays.sort(nums,i+1,nums.length);
}
public void swap(int[] nums,int i,int j){
int t;
t=nums[i];
nums[i]=nums[j];
nums[j]=t;
}
1326541:看成是132+一个倒序数6541,有它得到所需答案是有方法的,同时一切数都可以看成是前面几个数加倒序数,因此就得到了通解。
对于1326541:从后往前找直到找到2,此时无法满足倒序。
搜索2后面的几个数,谁稍微大于2,是4,此时2和4互调:1346521,然后将4后面的6521按照升序排列:134 1256得到答案
16 最长有效括号
给定一个只包含 '('
和 ')'
的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())
" 输出: 4 解释: 最长有效括号子串为"()()"
解:
public int longestValidParentheses(String s) {
int max = 0;
int[] dp = new int[s.length()];
for(int i=dp.length-2;i>=0;i--){
if(s.charAt(i)=='('){
// 对于特殊的情况就是看看是否越界
if(i+1<s.length() && s.charAt(i+1)==')'){
dp[i]=2+ ( i+2>s.length()-1?0:dp[i+2] );
max=Math.max(max,dp[i]);
}else {
// 对于特殊的情况就是看看是否越界
if(i+1<s.length() && i+1+dp[i+1]<s.length() && s.charAt(i+1+dp[i+1])==')'){
dp[i]=2+dp[i+1]+ ( i+2+dp[i+1]>s.length()-1?0:dp[i+2+dp[i+1]] ) ;
max=Math.max(max,dp[i]);
}
}
}
}
return max;
}
此题不能直接作为某个递归问题的特例,因此间接想,先考虑普通再到特例,有无分情况讨论:
给定一个字符串,每个字符确定一个以该字符打头的最长合法子串,因此确定一个长度,所以:
dp(i):最长合法子串的长度,该子串以第i个字符打头
那么最终问题就是求dp数组里面的最大值
合法子串的第一个字符肯定是'(',所以若s[i]==')',则dp[i]==0,只用考虑s[i] == '('了
分为2种情况:
对于第 i 项后面是 ( 的:
索引: i i+dp[i+1]+1 i+dp[i+1]+2
) ( ( ) ) (
dp: 2
s[ i+dp[i+1]+1] == ')' => dp[ i ] =2 + dp[ i+1 ] + dp[ i+dp[i+1]+2 ]
对于第 i 项后面是 ) 的:
索引: i i+2
) ( ) ( ) (
dp:
dp[ i ] = 2 + dp[ i+2 ]
其余dp位置的都是0
18爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
解:
public int climbStairs(int n) {
int p = 0;
int q = 1;
int sum =0;
for (int i = 0; i < n; i++) {
sum = p + q;
p = q;
q =sum;
}
return sum;
}
考虑递推,令f(n)即为所求,考虑f(n)与前后的关系:f( n ) = f( n-1 ) + f( n-2 )
为了降低时间复杂度,采用循环代替递归
为了降低空间复杂度,采用滚动数组
为了统一化管理,虚拟2个头结点:0(p) 1(q)
往后就是:
n:1=>1;
n:2=>2;
n:3=>3
19打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 400
解:
public int rob(int[] nums) {
if (nums.length==0){
return 0;
}
if(nums.length==1){
return nums[0];
}
if(nums.length==2){
return Math.max(nums[0],nums[1]);
}
int p=nums[0]; // f(n-2)
int q=Math.max(nums[0],nums[1]); // f(n-1)
int re=0; // f(n)
for(int i=2;i<nums.length;i++){
re = Math.max(nums[i]+p,q);
p=q;
q=re;
}
return re;
}
记f(n)即为所求,由于要写递推公式,所以从后往前考虑
分类法,若偷最后一间,则f(n) = nums[n-1]+ f(n-2);若不偷,则f(n) = f(n-1);
综上:f( n ) = max( nums[n-1]+ f(n-2) ,f( n-1 ) )
此处还可以采用:循环+滚动数组
20 分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
- 每个数组中的元素不会超过 100
- 数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5] 输出: true 解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5] 输出: false 解释: 数组不能分割成两个元素和相等的子集.
解:
先将问题做转化:能否从数组中挑出一些元素使得和=数组和的一半,这样右边就是定数target了
用是否来讨论,由于有2个变量在变动,干脆作为i,j,创建一个二维数组
d[ i ][ j ]:根据数组的前i个元素,能否找出几个元素使得之和 = j
若要arr[ i ],则d[ i ][ j ] = d[ i-1 ][ j-arr[ i ] ]
若不要它,则d[ i ][ j ] = d[ i-1 ][ j ]
所以:d[ i ][ j ] = d[ i-1 ][ j ] || d[ i-1 ][ j-arr[ i ] ]
最后返回d[ n-1 ][ target ]