LeetCode题——总共174道
一、简单
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) {
//将需要返回的下边存放在一个数组里面
int[] re=new int[2];
//然后通过遍历数组进行查找怎么可以达到这个这个目标值
for(int i=0;i 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//经过分析以后我们知道这个题需要我们进行从低位开始进行两位数的相加
ListNode res=null;//返回结果
boolean up=false;//是否进位
//如果l1和l2有一个不为null,则计算下一位的值
while(l1!=null || l2!=null){
int x=(l1==null?0:l1.val)+(l2==null?0:l2.val)+(up?1:0);
if(up){
up=false;
}
if(x>9){
ListNode temp=new ListNode(x%10);
temp.next=res;
res=temp;
up=true;
}else{
ListNode temp=new ListNode(x);
temp.next=res;
res=temp;
}
//将l1和l2同时往后走一个
l1=l1==null?null:l1.next;
l2=l2==null?null:l2.next;
}
//计算结束以后进行判断是否还有进位,如果进位的这一位为true了,那么整体再进一位
if(up){
//相当于是进行链表的头插
ListNode temp=new ListNode(1);
temp.next=res;
res=temp;
}
//然后将结果进行链表的反转
return reverse(res);
}
//反转链表的方法
public ListNode reverse(ListNode head){
if(head==null||head.next==null){
return head;
}
ListNode reHead=reverse(head.next);
head.next.next=head;
head.next=null;
return reHead;
}
}
3. 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
//分析:首先将字符串转化为字符数组
char[] str =s.toCharArray();
int len =s.length();
if(len==0) return 0;
if(len==1) return 1;
//HashSet set = new HashSet<>();
int i=0;
int j ,k,max=0;
for(j = 0;j max)
max = j-i+1;
}
return max;
}
}
4. 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
代码:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
//合并两个数组
int[] arr = new int[nums1.length + nums2.length];
for (int i = 0; i < len1; i++) {
arr[i] = nums1[i];
}
//int index = len1;
for (int i = 0; i < len2; i++) {
arr[len1+i] = nums2[i];
}
//将合并的数组进行排序
Arrays.sort(arr);
if ((arr.length - 1) % 2 == 0) {
return arr[(arr.length - 1) / 2];
} else {
int flag = (arr.length - 1) / 2;
return ((double)arr[flag] + (double)arr[flag + 1]) / 2;
}
}
}
5.给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
代码:
class Solution {
public int reverse(int x) {
//这道题可以通过将整型转化为字符串型,让你后通过字符串的反转函数进行反转
//(1)首先进行判断这个数是否小于0,然后将所有的数字转化为大于0进行处理
boolean isNegative=x<0;
if(isNegative){
x=-x;
}
//(2)将整型转化为字符串
String s=String.valueOf(x);
//(3)将String类型转化为StringBuilder为了实现后面的字符串拼接
StringBuilder reverseS=new StringBuilder(s);
//因为拼接是拼接到字符串的最后面,所以通过反转以后负号就到了最前面
if(isNegative){
reverseS.append("-");
}
//直接使用字符串反转
reverseS.reverse();
try{
//将字符串转化为整型返回
return Integer.parseInt(reverseS.toString());
}catch(NumberFormatException e){
return 0;
}
}
}
6. 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
代码:
class Solution {
public boolean isPalindrome(int x) {
//首先进行分析负数一定不是回文数
while(x<0){
return false;
}
//将一个整数转化为字符串,然后将这个字符串进行反转,如果反转以后的字符串和原来的相同那个就是回文整数
String s=String.valueOf(x);
//需要注意的是:字符串反转的方法只有StringBuilder里面才有,String里面是没有的
//所以我们需要将String字符串转化为StringBuilder,处理完成以后还需要转化为String
//因为他们进行equals比较的许霆要是同类型
String reverseS=new StringBuilder(s).reverse().toString();
if(reverseS.equals(s)){
return true;
}
return false;
}
}
7. 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
• I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
• X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
• C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
示例 1:
输入: "III"
输出: 3
示例 2:
输入: "IV"
输出: 4
示例 3:
输入: "IX"
输出: 9
示例 4:
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
代码:
class Solution {
public int romanToInt(String s) {
int n = s.length();
int roman_int = 0;
for(int i=0;i1){
int len=strs[0].length();
for(int i=0;i stack=new Stack<>();
//接下来对字符串进行分情况判断——
//但是需要注意的是我们传进去的参数是String类型的,但是我们在判断的时候是单个字符进行判断的,所以需要将字符串转化为字符数组
char[] ch=s.toCharArray();
for(int i=0;i2->4, 1->3->4
输出:1->1->2->3->4->4
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null){
return l2;
}
if(l2==null){
return l1;
}
ListNode cur1=l1;
ListNode cur2=l2;
ListNode result=null;
ListNode last=null;
while(cur1!=null&&cur2!=null){
if(cur1.val<=cur2.val){
ListNode next =cur1.next;
cur1.next=null;
if(result==null){
result=last=cur1;
}else{
last.next=cur1;
last=cur1;
}
cur1=next;
}else{
ListNode next =cur2.next;
cur2.next=null;
if(result==null){
result=last=cur2;
}else{
last.next=cur2;
last=cur2;
}
cur2=next;
}
}
if(cur2!=null){
last.next=cur2;
}
if(cur1!=null){
last.next=cur1;
}
return result;
}
}
11.删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
代码:
class Solution {
public int removeDuplicates(int[] nums) {
int cur1=0;
int cur2=1;
while(cur2nums[cur1]){
//但是要往后走一个才移动
cur1++;
//通过判断前后两个数是否相等如果出现相等直接将后面的数移到前面
nums[cur1]=nums[cur2];
}
cur2++;
}
return cur1+1;
}
}
12. 移除元素
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
代码:
class Solution {
public int removeElement(int[] nums, int val) {
int k=0;//[0,k)区间非val:即这个k是最后没有val值数组区间上限
for(int i=0;i=target){
return i;
}
}
//遍历完了整个数组还是没有找到满足条件的值,并且数组里面的所有值都小于目标值target
return nums.length;
}
}
15. 报数
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
1 被读作 "one 1" ("一个一") , 即 11。
11 被读作 "two 1s" ("两个一"), 即 21。
21 被读作 "one 2", "one 1" ("一个二" , "一个一") , 即 1211。
给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。
注意:整数顺序将表示为一个字符串。
示例 1:
输入: 1
输出: "1"
示例 2:
输入: 4
输出: "1211"
代码:
class Solution {
public String countAndSay(int n) {
if(n == 1) return "1";
if(n == 2) return "11";
String s = "";//用来存放前面一个数
if(n >1 ) {
s = countAndSay(n-1);
}
int temp = 1;//这个temp就是用来计数当前字符有几个
char c = s.charAt(0);
StringBuilder sb = new StringBuilder();
for(int i = 1;igreatestSum){
greatestSum=cursum;
}
}
return greatestSum;
}
}
17. 最后一个单词的长度
给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指由字母组成,但不包含任何空格的字符串。
示例:
输入: "Hello World"
输出: 5
代码:
class Solution {
public int lengthOfLastWord(String s) {
if(s.length()==0||s.equals(" ")||s.equals(" ")){
return 0;
}
//首先将这个单词按照空格进行拆分
String[] strs=s.split(" ");
if(strs.length==1){
String curStr=strs[0];
return strs[0].length();
}
//如果不是只有一个元素,那么就取最后一个元素
String lastStr=strs[strs.length-1];
return lastStr.length();
}
}
18.加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
示例 2:
输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
代码:
class Solution {
public int[] plusOne(int[] digits) {
//需要分情况讨论:当后一位不需要进位时/后一位需要进位时
int lastIndex=digits.length-1;
if(digits[lastIndex]<9){
digits[lastIndex]++;
return digits;
}
//否则是需要进位的:需要进位又分为是不是每一位都需要进位
//如果是后面的每一位都需要进位那么这个数组就需要进行扩容,扩大一个位数即可
int[] newNum=new int[lastIndex+2];
int i;
for(i=lastIndex;i>=0&&digits[i]==9;i--){
digits[i]=0;
}
if(i<0){//表示后面的每一位都是9都需要进行进位
newNum[0]=1;
return newNum;
}else{//表示进位到某一位的时候不需要继续进行进位了
digits[i]++;
return digits;
}
}
}
19.二进制求和
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1 和 0。
示例 1:
输入: a = "11", b = "1"
输出: "100"
示例 2:
输入: a = "1010", b = "1011"
输出: "10101"
代码:
class Solution {
public String addBinary(String a, String b) {
int i=a.length()-1,j=b.length()-1,jiwei=0,sum=0;
StringBuilder sb=new StringBuilder();
while (i>=0||j>=0){
sum=jiwei;
if (i>=0){
sum+=a.charAt(i--)-'0';
}
if (j>=0){
sum+=b.charAt(j--)-'0';
}
jiwei=sum/2;
sb.insert(0,sum%2);
}
if (jiwei==1)sb.insert(0,1);
return sb.toString();
}
}
20. x的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
代码:
class Solution {
public int mySqrt(int x) {
double temp=Math.sqrt(x);
//再将浮点型转化为整型
int result=(int) temp;
return result;
}
}
21.爬楼梯
假设你正在爬楼梯。需要 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 阶
代码:
class Solution {
public int climbStairs(int n) {
//这道题相当于是斐波那契额数列
if(n==1){
return 1;
}
if(n==2){
return 2;
}
//使用递归算法的时候超出了限制就是用循环
int re1=1;
int re2=2;
int re=0;
for(int i=2;i1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
/**采用前后指针的方法进行判断:
如果前面的指针指向元素的值不等于后面指针指向元素的值,那么前后指针同时向后走一步;
如果前面指针指向的元素等于后面指针指向的元素,那么后面的指针向后走一步再进行判断,如果不相等
则将当前的值赋值给前面指针的next,如果相等则再向后走一步*/
if(head == null || head.next==null){
return head;
}
ListNode cur1 = head;
ListNode cur2 = head.next;
while (cur2 != null) {
if (cur1.val == cur2.val) {
cur1.next = cur2.next ;
}else {
cur1 = cur1.next ;
}
//无论如何cur2都会向后走一步
cur2 = cur2.next;
}
return head;
}
}
23.合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
代码:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
//可以采用java中的数组排序算法
if(m==0){
if(n!=0){
for(int i=0;i inorderTraversal(TreeNode root) {
//二叉树的前序遍历的非递归实现:
//我们用一个栈来存放我们遍历的结点,然后通过查看访问结点的次数就可以知道前序遍历的结果
Stack s=new Stack<>();
List list=new ArrayList<>();
//我们进行遍历时的当前结点
TreeNode cur=root;
//当前栈顶的结点
TreeNode top=null;
while(cur!=null||!s.empty()){
//实现一路向左把结点放进栈里面
while(cur!=null){
s.push(cur);
cur=cur.left;
}
//这个地方表示第二次遇到这个结点
top=s.peek();
list.add(top.val);
s.pop();
cur=top.right;
}
return list;
}
}
25.相同的树
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1:
输入: 1 1
/ \ / \
2 3 2 3
[1,2,3], [1,2,3]
输出: true
示例 2:
输入: 1 1
/ \
2 2
[1,2], [1,null,2]
输出: false
示例 3:
输入: 1 1
/ \ / \
2 1 1 2
[1,2,1], [1,1,2]
输出: false
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/**
分析:我们分析题意可以知道,比较两颗二叉树是否相等总共分为三种情况:
两个都为空树发的时候必然是相等的;
一个为空树一个不为空树是不相等的;
两个都不为空树则分别进行比较两个的左子树和右子树;
*/
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null && q==null){
return true;
}
if(p!=null && q==null){
return false;
}
if(p==null && q!=null){
return false;
}
//下面用的相当于是前序遍历:根节点就是相等就直接输出了——左子树——右子树
//如果这个地方写相等那么会出现一个问题就是在两棵树里面只要遇到了相同的结点就会返回true
//所以这个地方我们需要判断不相等。
if(p.val!=q.val){
return false;
}
if(isSameTree(p.left,q.left)==false){
return false;
}
if(isSameTree(p.right,q.right)==false){
return false;
}
//如果上面的都不满足,那就是经过比较所有的结点都是相等的,返回true
return true;
}
}
26.对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/**
*对称二叉树:我们最重要的就是要将它分开成两棵树进行判断,对于根节点我们单独考虑。
如果一棵树只有一个根节点,那么这棵树一定是二叉树。否则我们将这棵树的左右子树进行镜像比较;
*/
class Solution {
/*
我们首先编写一个函数来实现镜像比较
*/
public boolean isMirrorTree(TreeNode p,TreeNode q){
if(p==null&&q==null){
return true;
}
//这个地方必须包含所有为空的情况,不然下面的引用就会出现空指针异常
//这两种写法是等价的:if((p!=null&&q==null)||(p==null&&q!=null))<==>(p==null||q==null)
if((p!=null&&q==null)||(p==null&&q!=null)){
return false;
}
return (p.val==q.val&&
isMirrorTree(p.left,q.right)&&
isMirrorTree(p.right,q.left));
}
public boolean isSymmetric(TreeNode root) {
//下面其实就是使用前序遍历实现对称二叉树的判断
if(root==null){
return true;
}
//对于对称二叉树我们的终止条件需要对一个结点单独考虑,一个结点的就是对称二叉树
if(root!=null&&root.left==null&&root.right==null){
return true;
}
return isMirrorTree(root.left,root.right);
}
}
27.二叉树的层序遍历
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List> levelOrder(TreeNode root) {
//这个层序遍历返回的结果是一个List集合,但是这个结合比较特殊,因为最后需要的是一个二维数组
//就是在List集合里面存放的还是List集合。
//(1)根节点为空
if(root==null){
//返回一个空的顺序表
return new ArrayList<>();
}
//我们需要一个队列来实现中间的转化关系:就是将一颗二叉树首先放进一个队列里面,然后取出来放进集合里面
Queue queue=new LinkedList<>();
//再定义一个集合来存放返回的结果
List> result=new ArrayList<>();
//根节点不为空的情况
queue.add(root);
while (!queue.isEmpty()){
//用于计数队列里面还有几个数
int count= queue.size();
List list=new ArrayList<>();
while (count>0){
//(1)将根节点放进集合里面了
TreeNode top=queue.peek();
queue.poll();
list.add(top.val);
//再判断它的左子树
if(top.left!=null){
queue.add(top.left);
}
//再判断右子树
if(top.right!=null){
queue.add(top.right);
}
count--;
}
//如果跳出了上面的循环以后就结束了遍历整个二叉树,此时将这个集合放进一个集合里面
//这个result就是一个二位的数组——二维顺序表
result.add(list);
}
return result;
}
}
28.二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
int leftMax=maxDepth(root.left);
int rightMax=maxDepth(root.right);
return leftMax>rightMax?leftMax+1:rightMax+1;
}
}
29.从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode build(int[] preorder,int[] inorder,int startPre,int endPre,int startIn,int endIn){
if(startPre>endPre){
return null;
}
if(startPre==endPre){
return new TreeNode(preorder[startPre]);
}
int rootval=preorder[startPre];
TreeNode root=new TreeNode(rootval);
//在中序遍历里面找到属于二叉树的左子树和右子树的结点
for(int i=startIn;i<=endIn;i++){
if(inorder[i]==rootval){
root.left=build(preorder,inorder,startPre+1,startPre+i-startIn,startIn,i-1);
root.right=build(preorder,inorder,startPre+i-startIn+1,endPre,i+1,endIn);
break;
}
}
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
return build(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
}
30.从中序与后序遍历序列构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
return realTree(inorder,0,inorder.length,postorder,0,postorder.length);
}
public TreeNode realTree(int[] inorder,int a,int b,int[] postorder,int c,int d){
if(a==b || c==d)
return null;
TreeNode node=new TreeNode(postorder[d-1]);
for(int i=a;irightMax?leftMax+1:rightMax+1;
}
// public int getHight(TreeNode root){
// if(root==null){
// return 0;
// }
// int leftHight=getHight(root.left);
// int rightHight=getHight(root.right);
// return leftHight>rightHight?leftHight+1:rightHiht+1;
// }
public boolean isBalanced(TreeNode root) {
if(root==null){
return true;
}
//如果左子树不为平衡二叉树则整棵树一定不是平衡二叉树
boolean leftBanlance=isBalanced(root.left);
if(!leftBanlance){
return false;
}
//如果右子树不是平衡二叉树那么这棵树也一定不是平衡二叉树
boolean rightBanlance=isBalanced(root.right);
if(!rightBanlance){
return false;
}
int leftHight=maxDepth(root.left);
int rightHight=maxDepth(root.right);
int abslute=leftHight-rightHight;
// if(abslute<0){
// abslute=abslute*(-1);
// }
if(abslute>=-1&&abslute<=1){
return true;
}
else{
return false;
}
}
}
32.二叉树的层序遍历
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其自底向上的层次遍历为:
[
[15,7],
[9,20],
[3]
]
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List> levelOrderBottom(TreeNode root) {
if(root==null){
return new ArrayList();
}
//这个集合是用来存放所有的数组
List> ans=new ArrayList<>();
Queue queue=new LinkedList<>();
queue.add(root);
//这个循环不为空表示一层里面的所有结点
while(!queue.isEmpty()){
//一层里面结点的总个数
int count=queue.size();
List list=new ArrayList<>();
while(count>0){
TreeNode node=queue.poll();
list.add(node.val);
if(node.left!=null){
//同时将下一层的结点添加进队列里面
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
count--;
}
//添加0表示这个元素是逆序存放的
ans.add(0,list);
}
return ans;
}
}
33.将有序数组转化为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
// 左右等分建立左右子树,中间节点作为子树根节点,递归该过程
return nums == null ? null : buildTree(nums, 0, nums.length - 1);
}
public TreeNode buildTree(int[] nums, int l, int r) {
if (l > r) {
return null;
}
int m = l + (r - l) / 2;
//中序遍历
TreeNode root = new TreeNode(nums[m]);
root.left = buildTree(nums, l, m - 1);
root.right = buildTree(nums, m + 1, r);
return root;
}
}
34.二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int minDepth(TreeNode root) {
//这个题通过前序遍历的方式来实现
if(root==null){
return 0;
}
int count=0;
Queue q=new LinkedList<>();
q.add(root);
while(!q.isEmpty()){
int n=q.size();
count++;
for(int i=0;i4->11->2。
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//对于二叉树这类的题都是这样的,先从根节点开始判断是否满足条件,在进行判断左右子树是否满足
public boolean hasPathSum(TreeNode root, int sum) {
if(root==null){
return false;
}
if(root.left==null&&root.right==null){
return sum-root.val==0;
}
return hasPathSum(root.left,sum-root.val) || hasPathSum(root.right,sum-root.val);
}
}
36.杨辉三角形
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
示例:
输入: 5
输出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
代码:
class Solution {
public List> generate(int numRows) {
if(numRows==0)return new ArrayList<>();
//这个表示每一层的数字
List> list1=new ArrayList<>();
//这个里面存放的是每一层里面的数字
List list2=new ArrayList<>();
//将第一层里面的数字添加到集合面
list2.add(1);
list1.add(list2);
if(numRows==1){//表示只需要杨辉三角形的第一层
return list1;
}
//如果大于1层
generate1(list1,2,numRows);
return list1;
}
public void generate1(List> list,int n,int numRow){
//n表示i总共需要多少行,numRow表示想在所在行
if(n==numRow+1) return;
List arr=new ArrayList<>();
arr.add(1);
for(int i=1;i getRow(int rowIndex) {
List res=new ArrayList<>(rowIndex+1);
long cur=1;
for(int i=0;i<=rowIndex;i++){
res.add((int) cur);
cur=cur*(rowIndex-i)/(i+1);
}
return res;
}
}
38.买股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
代码:
class Solution {
//1.记录今天之前买入的最小值
//2.计算今天之前最小值买入,今天卖出的获利,也就是今天卖出的最大获利
//3.比较每天的最大获利,取最大值即可
public int maxProfit(int[] prices) {
if(prices.length<=1){
return 0;
}
int min = prices[0],max=0;
for(int i=1;iprices[i-1]){
max+=(prices[i]-prices[i-1]);
}
}
return max;
}
}
40.验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false
代码:
class Solution {
public boolean isPalindrome(String s) {
//将这个字符串转化为StringBuilder的类型,然后通过他的逆置方法逆置以后进行判断是否相等
if(s==null){
return true;
}
s=s.toLowerCase();//转为小写,不区分大小写
int l=s.length();
//将String转化为StringBuilder
StringBuilder str=new StringBuilder(l);
for(char c:s.toCharArray()){
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) {
str.append(c);
}
}
return str.toString().equals(str.reverse().toString());
}
}
41.只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
代码:
class Solution {
// //1.方法1
// public int singleNumber(int[] nums) {
// //这个题的思路是首先对它进行排序,然后判断这个排序数组里面出现一次的数字
// //数组排序
// Arrays.sort(nums);
// for(int i=0;i node=new HashSet<>();
while(head!=null){
//如果在容器中出现了重复的元素那个就说明这个链表是循环的,但是如果遍历完了还是没有找到相等
//的元素,那么就说明没有相等的元素
if(node.contains(head))
return true;
else
node.add(head);
head=head.next;
}
return false;
}
// //2.快慢指针的方法
// public boolean hasCycle(ListNode head){
// if(head==null||head.next==null||head.next.next==null){
// return false;
// }
// ListNode slow=head.next;
// ListNode fast=slow.next;
// while(fast!=slow){
// if(fast.next==null||fast.next.next==null){
// return false;
// }
// slow=slow.next;
// fast=fast.next.next;
// }
// return true;
// }
}
43.最小栈
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
• push(x) -- 将元素 x 推入栈中。
• pop() -- 删除栈顶的元素。
• top() -- 获取栈顶元素。
• getMin() -- 检索栈中的最小元素。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
代码:
class MinStack {
/** initialize your data structure here. */
public MinStack() {
}
/**
对于这个最小栈的实现我们采用两个栈来实现,我们通过一次遍历来实现也就是时间复杂度为O(1)
但是我们的空间复杂度就不能是O(1)了,相当于是用时间来换空间。
对于这两个栈,一个栈里面存放我们入栈的元素,一个栈里面存放我们的最小元素
*/
//s1存放元素
Stack s1=new Stack<>();
//s2存放最小元素
Stack s2=new Stack<>();
public void push(int x) {
//对于s1的进栈没有变化就是正常进栈
s1.push(x);
//元素但是对于s2的进栈就需要考虑到比较每次进展元素的大小,始终保证是最小的
if(s2.empty()){
s2.push(x);
}else{
if(xtarget){
big_index--;
}
if(numbers[small_index]+numbers[big_index] A
2 -> B
3 -> C
...
26 -> Z
27 -> AA
28 -> AB
...
示例 1:
输入: 1
输出: "A"
示例 2:
输入: 28
输出: "AB"
示例 3:
输入: 701
输出: "ZY"
代码:
class Solution {
public String convertToTitle(int n) {
if(n<=0){
return "";
}
//首先A到Z和数字之间的转换是A=30
StringBuilder sb=new StringBuilder();
while(n>0){
n--;
sb.append((char)(n%26+'A'));
n=n/26;
}
return sb.reverse().toString();
}
}
48.Excel表列序号
给定一个Excel表格中的列名称,返回其相应的列序号。
例如,
A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28
...
示例 1:
输入: "A"
输出: 1
示例 2:
输入: "AB"
输出: 28
示例 3:
输入: "ZY"
输出: 701
代码:
import static java.lang.Math.pow;
class Solution {
//方法1:
// public int titleToNumber(String s) {
// if(s==null){
// return 0;
// }
// int len=s.length();
// int result=0;
// for(int i=0;i'9'))
return 0;
//否则就是第一位已经为有效的字符串
res=res+c;
while(i='0'&&str.charAt(i)<='9'){
res=res+str.charAt(i);
i++;
}
//如果这个字符串只有正负号那么也是返回0
if(res.equals("-")||res.equals("+"))
return 0;
BigInteger b = new BigInteger(res);
if(c == '-'){
if( b.compareTo(new BigInteger("-2147483648")) == -1) return Integer.MIN_VALUE;
}else{
if(b.compareTo(new BigInteger("2147483647")) == 1) return Integer.MAX_VALUE;
}
return Integer.valueOf(res);
}
}
51.盛最多水的容器
给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
代码:
import java.lang.Math;
class Solution {
public int maxArea(int[] height) {
//首先我们需要进行判断这个数组的长度是否大于1
if(height.length<=1){
return 0;
}
//否则我们分别从两端开始进行遍历
int max=0;
int low=0;
int high=height.length-1;
while(low> threeSum(int[] nums) {
List> list=new ArrayList<>();
Arrays.sort(nums);
//a+b+c=0——>a+b=-c
for(int i=0;i0 && nums[i]!=nums[i-1])){
int c=0-nums[i];//将它作为基准,从头到尾的遍历
int l=i+1;
int r=nums.length-1;
while(l list1=new ArrayList<>();//为什么这个集合只能在这个地方定义?
list1.add(nums[i]);
list1.add(nums[l]);
list1.add(nums[r]);
list.add(list1);
l++;
r--;
while(nums[l]==nums[l-1]&<arget){
r--;
}
else if(threeSum> fourSum(int[] nums, int target) {//将复杂问题简单化——转化为两个数字之和
List> list=new ArrayList<>();
Arrays.sort(nums);
for(int i=0;i0&&nums[i]!=nums[i-1])){//进到循环里面肯定就是没有重复的
for(int j=i+1;ji+1&&nums[j]!=nums[j-1]){
int l=j+1;
int r=nums.length-1;
int sum=target-nums[i]-nums[j];//还是转化为两数之和
while(l list1=new ArrayList<>();
list1.add(nums[i]);
list1.add(nums[j]);
list1.add(nums[l]);
list1.add(nums[r]);
list.add(list1);
while(lsum){
while(l list=new ArrayList<>();
String[] button=new String[]{"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List letterCombinations(String digits) {
if(digits==null || digits.length()==0)
return list;
letterCombinations(digits,0,new String());
return list;
}
public void letterCombinations(String digits,int index,String temp){
if(index==digits.length()){
list.add(temp);
return;
}
int position=digits.charAt(index)-'0';
String str=button[position];
for(int i=0;i2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
代码:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//使用两个结点,当first走到头的时候,second即是倒数第n+1个结点
ListNode first=head;
ListNode second=head;
//先让second先走几步
while(n-->0){
second=second.next;
}
if(second==null){
return head.next;
}
while(second.next!=null){
second=second.next;
first=first.next;
}
//如果跳出循环表示第二个结点已经走到末尾了:删除倒数第n个结点
first.next=first.next.next;
return head;
}
}
58.括号生成
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
代码:
class Solution {
List list=new ArrayList<>();
public List generateParenthesis(int n) {
recurison(list,n,n,"");
return list;
}
//使用递归调用:分别统计左括号和右括号的数量
public void recurison(List res,int left,int right,String s){
if(left>right){
return;
}
if(left==0&&right==0){
list.add(s);
}
if(left>0){
recurison(res,left-1,right,s+"(");
}
if(right>0){
recurison(res,left,right-1,s+")");
}
}
}
59.合并k个链表——分治算法
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
代码:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
//将一个复杂的问题简单化:将k个链表通过分治算法将他们拆分成两个链表的合并
if(lists.length==0){
return null;
}
if(lists.length==1){
return lists[0];
}
if(lists.length==2){
return mergeTwoLists(lists[0],lists[1]);
}
int mid=lists.length/2;
ListNode[] l1=new ListNode[mid];
for(int i=0;i2->3->4, 你应该返回 2->1->4->3.
代码:
class Solution {
public ListNode swapPairs(ListNode head) {
//递归终止条件
if(head==null||head.next==null){
return head;
}
//这个题是典型的递归函数调用,但是我们有一个问题就是说我们在递归函数调用的时候不需要去考虑每一级里面的具体过程
//在具体的每一级交换的时候我们就看成最简单的三个点进行交换就可以了
ListNode next=head.next;
head.next=swapPairs(next.next);
next.next=head;
return next;
}
}
61.两数相除
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
示例 1:
输入: dividend = 10, divisor = 3
输出: 3
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
说明:
• 被除数和除数均为 32 位有符号整数。
• 除数不为 0。
• 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。
代码:
/**
解题思路:
要求商,首先想到的是减法,看可以被减多少次商就是多少,但是这样的效率很低,所以使用移位算法
左移1相当于乘以2,右移一位相当于除以2
我们可以把一个dividend(被除数)先除以2^n,n开始为31,不断减小n去试探,当满足dividend/2^n>=divisor时,表示我们找到了一个足够大的数,(这个数 X divisor)是不大于dividend的,所以我们就减去2^n个divisor,依此类推。
*/
class Solution {
public int divide(int dividend, int divisor) {
if(dividend==0){
return 0;
}
if(dividend==Integer.MIN_VALUE&&divisor==-1){
return Integer.MAX_VALUE;
}
//判断符号是否相异
boolean negative=(dividend^divisor)<0;
long d1=Math.abs((long)dividend);
long d2=Math.abs((long)divisor);
int count=0;
for(int i=31;i>=0;i--){
if((d1>>i)>=d2){//找出足够大的数2^n*divisor
count+=1<nums[i-1]的第一个i值;
(2) 如果得到i值大于等于1,说明数组存在下一个排列;
1) 我们还是从数组最后一个元素开始扫描,寻找到nums[j]>nums[i-1]的第一个值。因为nums[i]>nums[i-1],所以我们的j值一定大于等于i的;
2) 交换索引为i-1和索引为j的元素的值;
3) 此时索引i及以后的排列是一个降序排列,将其变为升序排列;
(3)如果得到的i值小于1,说明数组不存在下一个排列,倒序输出数组即可;
代码:
class Solution {
public void nextPermutation(int[] nums) {
int i=nums.length-1;
for(;i>=1;i--){
if(nums[i]>nums[i-1]){
break;
}
}
if(i>=1){
int j=nums.length-1;
for(;j>=i;j--){
if(nums[j]>nums[i-1]){
break;
}
}
swap(i-1,j,nums);
reverse(nums,i);
}else{
reverse(nums,0);
}
}
public void reverse(int[] nums,int index){
int i=index;
int j=nums.length-1;
while(ihigh){
return -1;
}
int mid=(high+low)/2;
if(nums[mid]==target){
return mid;
}
//中间比最右边位置的数小,表示右边有序
if(nums[mid]target){
right=mid-1;
}else if(nums[mid]=0;i--){
if(nums[i]!=nums[flag]) break;
}
for(;j<=nums.length-1;j++){
if(nums[j]!=nums[flag]) break;
}
return new int[]{i+1,j-1};
}
}
65.组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
• 所有数字(包括 target)都是正整数。
• 解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
使用回溯算法——代码:
class Solution {
//回溯算法
List> res;
public List> combinationSum(int[] candidates, int target) {
res=new ArrayList<>();
getCombinationSum(candidates,target,0,0,new ArrayList<>());
return res;
}
public void getCombinationSum(int[] candidates,int target,int start,int sum,List list){
if(sum==target){
res.add(new ArrayList<>(list));
}
for(int i=start;i> res=new ArrayList<>();
public List> combinationSum2(int[] candidates, int target) {
//这个题目和前一道题目的区别是不能重复使用
Arrays.sort(candidates);
getCombination(candidates,0,0,target,new ArrayList<>());
return res;
}
public void getCombination(int[] nums,int start,int sum,int target,List list){
if(sum==target){
res.add(new ArrayList<>(list));
//return;
}
//定义一个前一次出现的数字
int removed=-1;
//因为在回溯算法里面它是将所有的可能都遍历一遍,所以我们就通过定义一个前一次出现的数字来排出相同的这种可能
for(int i=start;i 0; i--) {
// if (res[i] > 9) {
// res[i - 1] += res[i] / 10;
// res[i] %= 10;
// }
// }
// StringBuilder sb = new StringBuilder();
// Arrays.stream(res).forEach(sb::append);
// return sb.toString();
/*
*方法二:
*/
/**
num1的第i位(高位从0开始)和num2的第j位相乘的结果在乘积中的位置是[i+j, i+j+1]
例: 123 * 45, 123的第1位 2 和45的第0位 4 乘积 08 存放在结果的第[1, 2]位中
index: 0 1 2 3 4
1 2 3
* 4 5
---------
1 5
1 0
0 5
---------
0 6 1 5
1 2
0 8
0 4
---------
0 5 5 3 5
这样我们就可以单独都对每一位进行相乘计算把结果存入相应的index中
**/
int n1 = num1.length()-1;
int n2 = num2.length()-1;
if(n1 < 0 || n2 < 0) return "";
int[] mul = new int[n1+n2+2];
for(int i = n1; i >= 0; --i) {
for(int j = n2; j >= 0; --j) {
int bitmul = (num1.charAt(i)-'0') * (num2.charAt(j)-'0');
bitmul += mul[i+j+1]; // 先加低位判断是否有新的进位
mul[i+j] += bitmul / 10;
mul[i+j+1] = bitmul % 10;
}
}
StringBuilder sb = new StringBuilder();
int i = 0;
// 去掉前导0
while(i < mul.length-1 && mul[i] == 0)
i++;
for(; i < mul.length; ++i)
sb.append(mul[i]);
return sb.toString();
}
}
68.全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
代码:
class Solution {
List> res;
public List> permute(int[] nums) {
//回溯算法,遍历所有可能的结果
res=new ArrayList<>();
int[] visited=new int[nums.length];//数组里面的元素每一个对应一个标志位:是否有使用过这个元素
Arrays.sort(nums);
getPermute(nums,new ArrayList<>(),visited);
return res;
}
public void getPermute(int[] nums,List list,int[] visited){
//递归终止条件
if(list.size()==nums.length){
res.add(new ArrayList<>(list));
return;
}
for(int i=0;i> res;
List list;
boolean[] visited;
//有重复数字的全排列:我们需要做的就是将每次排列中相同的去掉
public List> permuteUnique(int[] nums) {
visited=new boolean[nums.length];
res=new ArrayList<>();
list=new ArrayList<>();
Arrays.fill(visited,true);//给数组赋值:前提是赋的值全部是一样的
Arrays.sort(nums);
int n=nums.length;
getPermuteUnique(nums,n);
return res; }
public void getPermuteUnique(int[] nums,int n){
if(list.size()==n){
res.add(new ArrayList<>(list));
return;
}
int mark=Integer.MAX_VALUE;//记录该层目录的元素
for(int i=0;i=endX){
return;
}
//具体关系如下
for(int i=startX;i> groupAnagrams(String[] strs) {
//使用HashMap来实现快速的查找
List> res=new ArrayList<>();
HashMap> map=new HashMap<>();
if(strs==null||strs.length==0){
return res;
}
for(int i=0;i());
}
//如果相等,则直接将这个词添加到该key对应的集合里面
map.get(key).add(strs[i]);
}
return new ArrayList<>(map.values());
}
}
72.Pow(x,n)
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
说明:
• -100.0 < x < 100.0
• n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
分析:采用的是折半计算方法:将n进行对半的缩小,n一定会缩小到0,等到n缩小到0以后我们知道任何一个数的0次方等于1,再往回乘,如果这个数是偶数直接将上次得到的值进行平方即可得到结果,但是如果是奇数的话这个结果还需要再乘以一个x;还需要注意对于n为正负结果是不同的。
代码:
class Solution {
public double myPow(double x, int n) {
double res=1.0;
for(int i=n;i!=0;i=i/2){
if(i%2!=0){
res=res*x;
}
x=x*x;
}
return n>0?res:1/res;
}
}
73.螺旋矩阵
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
示例 2:
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出: [1,2,3,4,8,12,11,10,9,5,6,7]
代码:
class Solution {
List list ;
public List spiralOrder(int[][] matrix) {
//这道题和前面的顺时针旋转矩阵的类似的
list = new ArrayList<>();
if (matrix.length == 0 || matrix[0].length == 0) {
return list;
}
int y1 = 0, y2 = matrix.length - 1;
int x1 = 0,x2 = matrix[0].length - 1;
while (y1 <= y2 && x1 <= x2) {
//先是行循环
for (int i = x1; i <= x2; i++) {
list.add(matrix[y1][i]);
}
//这个是扣边界 以下三个跟这个类似的if语句都是扣边界,让x,y不要出去这个边界。
if (y1 == y2) break;
y1 ++;
//列循环
for (int j = y1; j <= y2; j++) {
list.add(matrix[j][x2]);
}
if (x1 == x2) break;
x2--;
//然后再是行循环
for (int i = x2; i >= x1; i--) {
list.add(matrix[y2][i]);
}
if (y1 == y2) break;
y2--;
//最后列循环
for (int j = y2; j >= y1; j--) {
list.add(matrix[j][x1]);
}
if (x1 == x2) break;
x1++;
}
return list;
}
}
74.跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
方法 3:自底向上的动态规划
底向上和自顶向下动态规划的区别就是消除了回溯,在实际使用中,自底向下的方法有更好的时间效率因为我们不再需要栈空间,可以节省很多缓存开销。更重要的事,这可以让之后更有优化的空间。回溯通常是通过反转动态规划的步骤来实现的。
这是由于我们每次只会向右跳动,意味着如果我们从右边开始动态规划,每次查询右边节点的信息,都是已经计算过了的,不再需要额外的递归开销,因为我们每次在 memo 表中都可以找到结果。
Java
enum Index {
GOOD, BAD, UNKNOWN
}
public class Solution {
public boolean canJump(int[] nums) {
Index[] memo = new Index[nums.length];
for (int i = 0; i < memo.length; i++) {
memo[i] = Index.UNKNOWN;
}
memo[memo.length - 1] = Index.GOOD;
for (int i = nums.length - 2; i >= 0; i--) {
int furthestJump = Math.min(i + nums[i], nums.length - 1);
for (int j = i + 1; j <= furthestJump; j++) {
if (memo[j] == Index.GOOD) {
memo[i] = Index.GOOD;
break;
}
}
}
return memo[0] == Index.GOOD;
}
}
复杂度分析
时间复杂度:O(n^2)O(n 2 ),数组中的每个元素,假设为 i,需要搜索右边相邻的 nums[i] 个元素查找是否有 GOOD 的坐标。 nums[i] 最多为 n,n 是 nums 数组的大小。
空间复杂度:O(n)O(n),记忆表的存储开销。
方法 4:贪心
当我们把代码改成自底向上的模式,我们会有一个重要的发现,从某个位置出发,我们只需要找到第一个标记为 GOOD 的坐标(由跳出循环的条件可得),也就是说找到最左边的那个坐标。如果我们用一个单独的变量来记录最左边的 GOOD 位置,我们就可以避免搜索整个数组,进而可以省略整个 memo 数组。
从右向左迭代,对于每个节点我们检查是否存在一步跳跃可以到达 GOOD 的位置(currPosition + nums[currPosition] >= leftmostGoodIndex)。如果可以到达,当前位置也标记为 GOOD ,同时,这个位置将成为新的最左边的 GOOD 位置,一直重复到数组的开头,如果第一个坐标标记为 GOOD 意味着可以从第一个位置跳到最后的位置。
模拟一下这个操作,对于输入数组 nums = [9, 4, 2, 1, 0, 2, 0],我们用 G 表示 GOOD,用 B 表示 BAD 和 U 表示 UNKNOWN。我们需要考虑所有从 0 出发的情况并判断坐标 0 是否是好坐标。由于坐标 1 是 GOOD,我们可以从 0 跳到 1 并且 1 最终可以跳到坐标 6,所以尽管 nums[0] 可以直接跳到最后的位置,我们只需要一种方案就可以知道结果。
Index 0 1 2 3 4 5 6
nums 9 4 2 1 0 2 0
memo U G B B B G G
Java
public class Solution {
public boolean canJump(int[] nums) {
int lastPos = nums.length - 1;
for (int i = nums.length - 1; i >= 0; i--) {
if (i + nums[i] >= lastPos) {
lastPos = i;
}
}
return lastPos == 0;
}
}
复杂度分析
时间复杂度:O(n)O(n),只需要访问 nums 数组一遍,共 nn 个位置,nn 是 nums 数组的长度。
空间复杂度:O(1)O(1),不需要额外的空间开销。
75.合并区间
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
代码:
class Solution {
public int[][] merge(int[][] intervals) {
if(intervals == null || intervals.length < 2)
return intervals;
Arrays.sort(intervals, new Comparator() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0];
}
});
ArrayList res = new ArrayList<>();
int len = intervals.length;
for(int i = 0 ; i < len ; i ++) {
int[] temp = new int[2];
temp[0] = intervals[i][0];
temp[1] = intervals[i][1];
while(i < len - 1 &&temp[1] >= intervals[i + 1][0]) {//寻找本区间的可合并区间,有的话就合并
temp[1] = Math.max(temp[1],intervals[i + 1][1]);
i++;//标记到下一个待确认区间
}
res.add(temp);
}
int[][] arr = new int[res.size()][2];
res.toArray(arr);//转化为数组
return arr;
}
}