leecode+剑指offer
Posted Coding With you.....
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leecode+剑指offer相关的知识,希望对你有一定的参考价值。
1.算法入门14天
1.704二分查找:
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
思路:因为是有序的数组,所以采用二分查找是相对快的方法。从中间开始,小于就后移大于就左移,当找到当前数组中的最后一个元素还没找到就返回-1
Tip:
- 计算 mid 时需要防止溢出,代码中 left + (right - left) / 2 就和 (left + right) / 2 的结果相同,但是有效防止了 left 和 right 太大直接相加导致溢出。
- 二分查找的一个技巧是:不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。
代码:
var search = function(nums, target)
let low=0,high=nums.length-1;
while( low<=high)
mid=Math.floor(low+(high-low)/2);
if(nums[mid]==target)
return mid ;
else if(nums[mid ]>target)
high=mid-1;
else
low=mid+1;
return -1;
;
2.278第一个错误的版本
题目描述:你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n
个版本 [1, 2, ..., n]
,你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version)
接口来判断版本号 version
是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
思路:如果mid是bad就像前找(第一个bad不可能在其后面),如果mid不是bad就想好找,第一个bad在后面
Tip:
- 找值:二分查找的循环条件应该加上等号,这样就是将一个元素的数组也进行了判断,如果判断就不确定该元素是否与目标匹配
- while(low<high)//这里填上= 并且high=mid-1
mid=Math.floor(low+(high-low)/2);
if(isBadVersion(mid)) high=mid;
else low=mid+1;
return mid;//这里填入low也行,因为high与low相等 但是填入mid就不对 解析错误。 因为这找的不是具体值
代码:
没有等于:判断完后直接是最后一个,mid一直被包含 就是如果mid前面没有bad的话那么就直接取mid
var solution = function(isBadVersion)
/**
* @param integer n Total versions
* @return integer The first bad version
*/
return function(n)
var low=1,high=n;
let mid=0;
while(low<high)//这里填上=也不对
mid=Math.floor(low+(high-low)/2);
if(isBadVersion(mid)) high=mid;
else low=mid+1;
return mid;//这里填入low也行,因为high与low相等 但是填入mid就不对 解析错误
;
有等于:如果左边都不是,那么需要加1得到mid 因为mid没有包含在后续检索的数组中
就是如果mid前面没有bad的话那么取mid需要再左边数组中加1才行
var solution = function(isBadVersion)
/**
* @param integer n Total versions
* @return integer The first bad version
*/
return function(n)
var low=1,high=n;
let mid=0;
while(low<=high)//这里填上=也不对
mid=Math.floor(low+(high-low)/2);
if(isBadVersion(mid)) high=mid-1;
else low=mid+1;
return low;
;
3.35搜索插入位置
题目描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
思路:如果mid是target就返回;如果mid不是target就二分找,找不到就返回low的位置:此时high=low-1,如果没找到就会有两种情况,一种是插在当前值前面(此时low没执行+1,且nums[low]>target 那就插入当前位置),一种是插在当前值后面(此时low执行了+1,且nums[low]<target 那就插入当前位置之后)
Tip:多举例子进行试
代码:
/**
* @param number[] nums
* @param number target
* @return number
*/
var searchInsert = function(nums, target)
var low=0,high=nums.length-1;
while(low<=high)
let mid=Math.floor(low+(high-low)/2);
if(nums[mid]==target) return mid;
else if(nums[mid]<target) low=mid+1;
else high=mid-1;
return low;
;
4.977. 有序数组的平方
题目描述:给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路:先用二分法找到第一个非负的值,得到正负分界low和high
采用双指针插入新的序列
时间复杂度是O(n)
其实采用O(n)最简单的思路是:采用两个指针,从两端进行比较倒序插入数组中
Tip:以后题目要求常量级别的空间复杂度,你只要注释一句“空间复杂度是「存储答案的数组以外」的结果”,就可以随便new大小为n的空间了
代码:
js:过程中要考虑的因素非常多:指针边界也就是刚开始的基准,如果全为正或者全为负怎么办;在合并排序中如果一个数组为空了另一个怎么办 这些情况都得考虑
/**
* @param number[] nums
* @return number[]
*/
var sortedSquares = function(nums)
let low1=0,high1=nums.length-1;
if(nums[nums.length-1]<0)
high1 =nums.length-1;
low1=nums.length ;
else if(nums[0]>0)
high1 =-1;
low1=0;
else
while(low1<=high1)
let mid= Math.floor(low1+(high1-low1)/2);
if(nums[mid]<0) low1=mid+1;
else high1=mid-1;
//high1 low1就是分界点,high1=low1-1
let result=[];
let i=0;
if(high1==-1)
while(i<nums.length)
result[i]=nums[i]*nums[i];
i++;
else if(low1==nums.length )
let j=nums.length-1;
while(j>-1)
result[j]=nums[i]*nums[i];
j--;
i++;
else
while(high1>=0 || low1<nums.length && i<nums.length )
if(high1<0)
result[i]=nums[low1]*nums[low1];
low1++;
else if(low1>=nums.length)
result[i]=nums[high1]*nums[high1];
high1--;
else
if(nums[low1]*nums[low1]>nums[high1]*nums[high1])
result[i]=nums[high1]*nums[high1];
high1--;
else
result[i]=nums[low1]*nums[low1];
low1++;
i++;
return result;
;
java
class Solution
public int[] sortedSquares(int[] nums)
int low1=0,high1=nums.length-1;
if(nums[nums.length-1]<0)
high1 =nums.length-1;
low1=nums.length ;
else if(nums[0]>0)
high1 =-1;
low1=0;
else
while(low1<=high1)
int mid=(int) Math.floor(low1+(high1-low1)/2);
if(nums[mid]<0) low1=mid+1;
else high1=mid-1;
//high1 low1就是分界点,high1=low1-1
int[] result=new int[nums.length];
int i=0;
if(high1==-1)
while(i<nums.length)
result[i]=nums[i]*nums[i];
i++;
else if(low1==nums.length )
int j=nums.length-1;
while(j>-1)
result[j]=nums[i]*nums[i];
j--;
i++;
else
while(high1>=0 || low1<nums.length && i<nums.length )
if(high1<0)
result[i]=nums[low1]*nums[low1];
low1++;
else if(low1>=nums.length)
result[i]=nums[high1]*nums[high1];
high1--;
else
if(nums[low1]*nums[low1]>nums[high1]*nums[high1])
result[i]=nums[high1]*nums[high1];
high1--;
else
result[i]=nums[low1]*nums[low1];
low1++;
i++;
return result;
5.189.轮转数组
题目描述:给你一个数组,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
思路:因为k非负,所以将k确保在数组长度内。然后进行轮转,向后移动,超过数组长度的部分向前移动
Tip:此题另辟空间的方法就失去了意义,应该是采用反转的思想:此题只需要采取三次翻转的方式就可以得到目标数组,首先翻转分界线前后数组,再整体翻转一次即可
修改后内存差不了多少,只是少了一个空间分配
代码:
js
反转
/**
* @param number[] nums
* @param number k
* @return void Do not return anything, modify nums in-place instead.
*/
function reverse(nums,i,j)
while(i<j)
let temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
i++;j--;
var rotate = function(nums, k)
span=k%nums.length;
if(nums.length<2) return;
//反转整体
reverse(nums,0,nums.length-1);
reverse(nums,0,span-1);
reverse(nums,span,nums.length-1);
;
/**
* @param number[] nums
* @param number k
* @return void Do not return anything, modify nums in-place instead.
*/
var rotate = function(nums, k)
let span=k%nums.length;
let res=[];
for (var i=0; i<nums.length; i++)
if(i+span <nums.length) res[i+span]=nums[i];
else res[i+span-nums.length]=nums[i];
for(var i=0;i<res.length;i++)
nums[i]=res[i];
;
java
class Solution
public void rotate(int[] nums, int k)
int res[]=new int[nums.length];
int span=k%nums.length;
for (int i=0; i<nums.length; i++)
if(i+span <nums.length) res[i+span]=nums[i];
else res[i+span-nums.length]=nums[i];
for(int i=0;i<res.length;i++)
nums[i]=res[i];
6.283.移动零
题目描述:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
思路: 暴力解法就是从后边往前移动,j最初指向最末尾,如果是0就指针j前移;如果不为0就再用一个指针i找前面的0 在找到0时就将i-j之间的前移并且j处变为0
Tip: 指定一个最外层的指针,记录并按顺序保存不为0的数,剩下的全部用0填充,这样的复杂度是o(n),时间明显缩短
同样代码使用Java执行的时间与空间更小
/**
* @param number[] nums
* @return void Do not return anything, modify nums in-place instead.
*/
var moveZeroes = function(nums)
var j=0;
for(var i=0;i< nums.length;i++ )
if(nums[i]!=0) nums[j++]=nums[i]
for(var m=j;m< nums.length;m++)
nums[m]=0;
;
java
var j=0;
for(var i=0;i< nums.length;i++ )
if(nums[i]!=0) nums[j++]=nums[i]
for(var m=j;m< nums.length;m++)
nums[m]=0;
代码:
js
/**
* @param number[] nums
* @return void Do not return anything, modify nums in-place instead.
*/
var moveZeroes = function(nums)
for(var j=nums.length-1;j>=0;j-- )
if(nums[j]==0) continue;
else
for(var i=j-1;i>=0;i--)
if(nums[i]==0)
for(var m=i;m<j;m++)
nums[m]=nums[m+1];
nums[j]=0;
break;
;
java
class Solution
public void moveZeroes(int[] nums)
for(int j=nums.length-1;j>=0;j-- )
if(nums[j]==0) continue;
else
for(int i=j-1;i>=0;i--)
if(nums[i]==0)
for(int m=i;m<j;m++)
nums[m]=nums[m+1];
nums[j]=0;
break;
7.167. 两数之和Ⅱ-输入有序数组
题目描述:给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
思路: X-最暴力解法是两层循环且两元素不相同就返回,返回即结束;
X-从中点开始,大于等于目标就取左半部找,小于就找是否有target-当前存在于数组中
取小于target的部分,遍历i看是否有target-i在数组中,是o(n^2)
同样代码下
Tip:
1.寻找第二个数时使用二分查找会使得复杂度降低
2.双指针:最好lgn 最坏o(n)
就是第一个指针指向初始,第二个指针指向末尾,如果和大于目标就右指针左移,如果和小于目标就左指针右移;如果相等就输出左+1,右+1
代码:
js
/**
* @param number[] numbers
* @param number target
* @return number[]
*/
var twoSum = function(numbers, target)
let a=[] ;
for(var i=0;i<numbers.length;i++)
for(var j=i+1;j<numbers.length;j++)
if(target-numbers[i] ==numbers[j])
a[0]=i+1;
a[1]=j+1;
return a;
return a;
;
java
class Solution
public int[] twoSum(int[] numbers, int target)
int a[]=new int[2];
for(int i=0;i<numbers.length;i++)
for(int j=i+1;j<numbers.length;j++)
if(target-numbers[i] ==numbers[j])
a[0]=i+1;
a[1]=j+1;
return a;
return a;
8. 344.反转字符串
题目描述: 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s
的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
思路: 头尾指针,当头与尾指针指向同一个数时不进行交换,直接退出循环
Tip:
1.反转可以使用位运算"异或":a=a^b,b=b^a,a=a^b等价于a与b交换
2.交换顺序的题,如果使用for循环的话,直接取<len/2就行不用考虑上界/下界
代码:
js
var reverseString = function(s)
let low=0;
let high=s.length-1;
while(low<high)
let temp=s[low];
s[low]=s[high];
s[high]=temp;
low++;
high--;
;
java
class Solution
public void reverseString(char[] s)
int low=0;
int high=s.length-1;
while(low<high)
char temp=s[low];
s[low]=s[high];
s[high]=temp;
low++;
high--;
9. 557.反转字符串中的单词Ⅲ
题目描述: 给定一个字符串 s
,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
思路: 1.以空格分隔为数组,对每一个词反转后采用空将数组拼接为字符串 o(n^2)
java拼接数组使用new String,js拼接使用.join()
2.采用两个指针,当指向空时将[左指针,右指针)进行反转,不等于空时继续找,最后进行反转:这种方法在Java等语言中不适用,因为string类型时不可变的,需要先转为数组才行 这种不对
Tip: 因此对于Java,需要额外开辟空间来减少时间复杂度:比如当为空格时将单词反转保存在新的空间中(从头到尾遍历一遍 s就行 为o(n)) 与思路1类似
代码:
js
/**
* @param string s
* @return string
*/
var reverseWords = function(s)
let e_word=s.split("");
let i=0;
let j=i+1;
while(i<e_word.length )
if(j>e_word.length -1) break;
if(e_word[i]==' ')i++;j=i+1; continue;
else
while(j<e_word.length )
if(e_word[j]==' ')
let tmp1=i;
let tmp2=j-1;
while(tmp1<tmp2 )
let mm=e_word[tmp1];
e_word[tmp1]=e_word[tmp2];
e_word[tmp2]=mm;
tmp1++;
tmp2--;
i=j+1;
j=i+1;
break;
else j++;
let tmp1=i;
let tmp2=j-1;
while(tmp1<tmp2 )
let mm=e_word[tmp1];
e_word[tmp1]=e_word[tmp2];
e_word[tmp2]=mm;
tmp1++;
tmp2--;
return e_word.join("");
;
java
class Solution
public String reverseWords(String s)
char[] e_word=s.toCharArray();
int i=0;
int j=i+1;
while(i<s.length())
if(j>s.length()-1) break;
if(e_word[i]==' ')i++;j=i+1; continue;
else
while(j<e_word.length )
if(e_word[j]==' ')
int tmp1=i;
int tmp2=j-1;
while(tmp1<tmp2 )
char mm=e_word[tmp1];
e_word[tmp1]=e_word[tmp2];
e_word[tmp2]=mm;
tmp1++;
tmp2--;
i=j+1;
j=i+1;
break;
else j++;
int tmp1=i;
int tmp2=j-1;
while(tmp1<tmp2 )
char mm=e_word[tmp1];
e_word[tmp1]=e_word[tmp2];
e_word[tmp2]=mm;
tmp1++;
tmp2--;
return new String(e_word);
10. 876. 链表的中间结点
题目描述: 给定一个头结点为 head
的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
思路: 获得链表的长度,然后遍历len/2处为中间节点
注意:在循环中如果是js中,len/2不为整数时会按照number型处理,因此会多一个值;当在java中不包含该值。比如该值=2.1,在js中包含2在Java中不包含2
Tip: 采用快慢指针,快指针走到结尾时慢指针走到了中间,这里注意:如果快指针的next不为0 那么说明是偶数个中间需要+1
js
var middleNode = function(head)
let count=1;
let p1 = head,p2 = head;
while(p2!=null && p2.next !=null)//这样就不需要管奇偶了
p1=p1.next;
p2=p2.next.next;
//if (p2.next==null) return p1; 如果是p2.next!=null && p2.next.next !=null作为条件就需要
//else return p1.next;
return p1;
;
java
class Solution
public ListNode middleNode(ListNode head)
int count=1;
ListNode p1 = head,p2 = head;
while(p2!=null && p2.next !=null)//这样就不需要管奇偶了
p1=p1.next;
p2=p2.next.next;
//if (p2.next==null) return p1; 如果是p2.next!=null && p2.next.next !=null作为条件就需要
//else return p1.next;
return p1;
代码:
js
var middleNode = function(head)
var count=0;
var head1 = head;
while(head.next!=null)
count++;
head=head.next;
for(var i=0;i<count/2;i++)
head1=head1.next;
return head1;
;
java
class Solution
public ListNode middleNode(ListNode head)
int count=1;
ListNode head1 = head;
while(head.next!=null)
count++;
head=head.next;
for(int i=0;i<count/2;i++)
head1=head1.next;
return head1;
11. 19. 删除链表的倒数第 N 个结点
题目描述: 给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
思路: 头节点不动,再写一个节点得到节点长度,如果长度满足条件就进行查找:也就是删除第len-n+1个节点,即head的 len-n个next 由于在头节点前创建一个节点便于删除头节点,因此删除的节点需要后移一个 第len-n+2个节点 / head的 len-n+1个next/head的 len-n个next后再next一下
Tip:
1.快慢指针:快指针先走n步慢指针再开始,这样当快指针的next走到末尾时,慢指针的next就是要删除的
2.采用栈
3.递归的思想:时间上可能慢些
代码:
js
var removeNthFromEnd = function(head, n)
let pre=new ListNode(0,head);//需要创建一个头节点,当删除头节点时便于删除 并且该节点不要动,最后返回其下一个
let p1=head ,p2=pre;//p1语义计算长度,p2用于删除节点
let count=0;
while(p1!=null)
p1=p1.next;
count++;
for(let i=0;i<count-n;i++) p2=p2.next;//因为p2时在头节点之前加的,所以相当于多了一个节点,真实删除的是p2.next
p2.next=p2.next.next;
return pre.next;
;
java
class Solution
public ListNode removeNthFromEnd(ListNode head, int n)
ListNode pre=new ListNode(0,head);//需要创建一个头节点,当删除头节点时便于删除 并且该节点不要动,最后返回其下一个
ListNode p1=head ,p2=pre;//p1语义计算长度,p2用于删除节点
int count=0;
while(p1!=null)
p1=p1.next;
count++;
for(int i=0;i<count-n;i++) p2=p2.next;//因为p2时在头节点之前加的,所以相当于多了一个节点,真实删除的是p2.next
p2.next=p2.next.next;
return pre.next;
12. 3. 无重复字符的最长子串
题目描述: 给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
思路: 采用hash表:键为当前值,值为对应索引,从头开始遍历:如果当前键在hash表中,判断有|无当前键哪个串的长度大,进行hash表的更新(左边索引的变化);如果当前键不在hash表中,不操作;记录结果后直接后移将每一个放入表中就行
如果找到与前面重复的字符,更新左指针;然后更新当前最大长度,继续添加
Tip: 不用管当前表中保存的值与长度,只是作为判断重复的依据|| 判断左指针位置,只要不断后移记录长度就行
2.先找到从第1个字符开始的最长串;再找从第二个开始的最长串(右指针从当前后移就行,不需要从当前i开始——左指针在外层变,右指针在里层变且是全局变量);一直到最后,保留最长的串的大小
记录以每个元素开头的最大长度,选择所有中的最大长度
代码:
js
var lengthOfLongestSubstring = function(s)
let map =new Set ();
let maxLen = 0;//用于记录最大不重复子串的长度
let rr=0;//右指针
for (let i = 0; i < s.length ; i++)//左指针
if(i!=0) map.delete (s.charAt(i-1));//每次删除前一个元素,相当于找以每个元素开头的最大子串
while(rr<s.length && ! map.has(s.charAt(rr)))//当不包含该字符时就添加 为了判断是否重复
map.add(s.charAt(rr));rr++;
maxLen=Math.max(maxLen, rr-i);//以第i个元素开头的串中,右指针-左指针
return maxLen;
;
java
class Solution
public int lengthOfLongestSubstring(String s)
HashMap<Character, Integer> map = new HashMap<>();
int maxLen = 0;//用于记录最大不重复子串的长度
int left = 0;//滑动窗口左指针
for (int i = 0; i < s.length() ; i++)
if(map.containsKey(s.charAt(i)))//判断有当前值还是没有的长度大,保存大的
left=Math.max(left, map.get(s.charAt(i))+1);//因为键是不可以重复的,如果键出现过好几次,表中记录的是第一次的位置
maxLen=Math.max(maxLen, i-left+1);
map.put(s.charAt(i), i);
return maxLen;
13. 567.字符串的排列
题目描述:
给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。
思路: 遍历s2中长度为是S1大小的窗口,需要两个指针:左和右并保存到map中;如果s2的内容都在map中就返回true,否则结束循环后返回false
不对:需要再加一步,考虑每一个词的词频,即两个序列排序后相等
Tip: 来来回回好几个小时,最后特别简单的代码:不用map 直接指针遍历计算词频
代码:
js
function arrayEqual(arr1, arr2)
if (arr1 === arr2) return true;
if (arr1.length != arr2.length) return false;
for (var i = 0; i < arr1.length; ++i)
if (arr1[i] !== arr2[i]) return false;
return true;
var checkInclusion = function a(s1, s2)
var array1 = new Array(26).fill(0);
var array2 = new Array(26).fill(0);
for (var i = 0; i < s1.length ; i++)
array1[s1.charAt(i).codePointAt()-97]++;
for(var i=0;i<s2.length ;i++)
for(var tm=0;tm<array2.length;tm++) array2[tm]=0;
for (var k = i; k < i+s1.length ; k++)
if(k<s2.length ) array2[s2.charAt(k).codePointAt()-97]++;
if( arrayEqual(array1,array2)) return true;//这里使用 if( array1.toString()==array2.toString() ) return true;也可以,用时少一些但内存大一些
return false;
;
java
class Solution
public boolean checkInclusion(String s1, String s2)
int[] array1 = new int[26];//存放出现频次
int[] array2 = new int[26];
for (int i = 0; i < s1.length(); i++)
array1[s1.charAt(i)-'a']++;
for(int i=0;i<s2.length();i++)//对于每一个窗口
for(int tm=0;tm<array2.length;tm++) array2[tm]=0; //每个窗口开始前清空原来的记录
for (int k = i; k < i+s1.length(); k++) //该窗口的频次数组
if(k<s2.length()) array2[ s2.charAt(k)-'a']++;
if(Arrays.equals(array1,array2)) return true;
return false;
;
14. 733.图像渲染
题目描述:
有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。
给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。
为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。
最后返回经过上色渲染后的图像。
思路: 首先通过给定的坐标值,通过从左到右 从上到下的顺序遍历,如果之与给定元素的值相同就改变其颜色
Tip: 看题解后那样完全不对,理解错题目了
是给定一个点要以其为中心进行渲染,渲染其上下左右;然后渲染其渲染后的上下左右.....
可以使用深度优先、广度优先、递归等方法
1.对于深度优先:遍历每一个节点及其子节点,结束后再遍历下一个节点
2.对于广度优先:遍历每一层的上下左右,然后再遍历上下左右每一个的上下左右
!!!!!一定要 等于旧的颜色不等于新的颜色才遍历填充,否则报错
代码:
java深度
class Solution
public int[][] floodFill(int[][] image, int sr, int sc, int newColor)
return dfs(sr,sc,image, image[sr][sc],newColor);
public static int[][] dfs(int a,int b,int[][] image,int oldColor,int newColor)
if(a>= 0 && b>=0 && a< image.length && b < image[0].length && image[a][b]!=newColor && image[a][b]==oldColor)
//改变当前颜色 包括深度搜索改变其所有的
int tmp=image[a][b];
image[a][b]=newColor;
dfs(a-1, b, image,tmp, newColor);
dfs(a+1, b, image, tmp, newColor);
dfs(a, b-1, image, tmp, newColor);
dfs(a, b+1, image, tmp, newColor);
return image;
广度
class Solution
public int[][] floodFill(int[][] image, int sr, int sc, int newColor)
//bfs 初始化一个根节点放入队列,进行填充 如果其有上下左右 也都放进去填充
int oldColor=image[sr][sc];
if (oldColor == newColor) return image;
Queue<int[]> queue = new LinkedList<int[]>();
queue.add(new int[]sr,sc);
while(!queue.isEmpty())
int[] j=queue.poll();
image[j[0]][j[1]]=newColor;
if (j[0] - 1 >= 0 && image[j[0] - 1][j[1]] == oldColor) queue.add(new int[] j[0]- 1, j[1] );
if (j[0]+ 1 < image.length && image[j[0] + 1][j[1]] == oldColor) queue.add(new int[] j[0] + 1, j[1] );
if (j[1] - 1 >= 0 && image[j[0]][j[1] - 1] == oldColor) queue.add(new int[] j[0], j[1] - 1);
if (j[1] + 1 < image[0].length && image[j[0]][j[1] + 1] == oldColor) queue.add(new int[] j[0], j[1] + 1);
return image;
15. 695. 岛屿的最大面积
题目描述:
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1
的单元格的数目。
计算并返回 grid
中最大的岛屿面积。如果没有岛屿,则返回面积为 0
。
思路:
- 找一个起始点开始遍历,从上到下从左到右开始,如果=1就调用dfs方法;
- 调用的时候相当于访问该元素,访问完需要给一个标记;
- 访问中岛屿最大面积为其自身1+上下左右四块之和
- 如果没有了=1的或者到达了边界就退出
Tip: 一定要标记访问过的,一定要有出口
代码:
js
var maxAreaOfIsland = function(grid)
var max_i=0;
var dfs=(grid,x,y)=>
if(x<0 || y<0 || x>=grid.length || y>=grid[0].length ||grid[x][y]==0 ||grid[x][y]==2) return 0;
grid[x][y]=2;//已经访问
return 1+dfs(grid,x,y+1)+dfs(grid,x,y-1)+dfs(grid,x-1,y)+dfs(grid,x+1,y);
for(var i=0;i<grid.length;i++)
for(var j=0;j<grid[0].length;j++)
max_i=Math.max(max_i, dfs(grid, i, j));
return max_i;
;
java
class Solution
public static int dfs(int[][] grid,int x,int y)
if(x<0 || y<0 || x>=grid.length || y>=grid[0].length ||grid[x][y]==0 ||grid[x][y]==2) return 0;
grid[x][y]=2;//已经访问
return 1+dfs(grid,x,y+1)+dfs(grid,x,y-1)+dfs(grid,x-1,y)+dfs(grid,x+1,y);
public int maxAreaOfIsland(int[][] grid)
int max_i=0;
for(int i=0;i<grid.length;i++)
for(int j=0;j<grid[0].length;j++)
max_i=Math.max(max_i, dfs(grid, i, j));
return max_i;
16. 617. 合并二叉树
题目描述:
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
思路: 该节点值更新+左子树与右子树遍历,都为空时返回
Tip:
代码:
js
var mergeTrees = function(root1, root2)
var dfs=( r1, r2) =>
// 如果 r1和r2中,只要有一个是null,函数就直接返回
if(r1==null || r2==null)
return r1==null? r2 : r1;
//让r1的值 等于 r1和r2的值累加,再递归的计算两颗树的左节点、右节点
r1.val += r2.val;
r1.left = dfs(r1.left,r2.left);
r1.right = dfs(r1.right,r2.right);
return r1;
return dfs(root1,root2);//这样的话参数是动态叠加的
;
java
class Solution
TreeNode dfs(TreeNode r1, TreeNode r2)
// 如果 r1和r2中,只要有一个是null,函数就直接返回
if(r1==null || r2==null)
return r1==null? r2 : r1;
//让r1的值 等于 r1和r2的值累加,再递归的计算两颗树的左节点、右节点
r1.val += r2.val;
r1.left = dfs(r1.left,r2.left);
r1.right = dfs(r1.right,r2.right);
return r1;
public TreeNode mergeTrees(TreeNode root1, TreeNode root2)
return dfs(root1,root2);//这样的话参数是动态叠加的
17. 116. 填充每个节点的下一个右侧节点指针
题目描述:
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node int val; Node *left; Node *right; Node *next;
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
思路: 当root为空时直接返回;如果不为空就获取上一层节点,如果该节点有左孩子存在该节点的左孩子指向右孩子,如果该节点next不为空就该节点右孩子指向next的左孩子;next为空后就进行下一层
Tip: 先判断为空时的出口;然后整体一层一层循环:先横向再纵向
代码:
class Solution
public Node connect(Node root)
if(root==null) return root;
Node leftn=root;
while(leftn.left !=null)
//遍历这一层,填充下一层
Node head=leftn;
while(head!=null)
head.left.next=head.right;
if(head.next!=null) head.right.next=head.next.left;
head=head.next;
leftn=leftn.left;
return root;
18. 542.01矩阵
题目描述:
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
思路: 如果为0就返回0;否则返回上 下 左右中距离0最小的一个再+1
Tip:
方法1.DFS :需要考虑,如果其周围有0和周围没有0的情况。就是为了减少时间,只递归周围没有0的这种1,周围有0的那距离就是1也就是其本身;周围没有0必有1,从1为入口进行深度搜索:找到周围没0的1就会进行更新,只要以当前的为初始 下一个就会+1
方法2.BFS :需要考虑,如果其为0就先放入队列中,不为0就付一个大值便于更新;每次取出一个值来更新其周围的不为0的元素;如果周围元素不为0就更新距离放入队列中 便于后续与他相关节点的更新;当队列为空时结束
方法1.动态规划 :就是该值通过上下左右进行更新,如果不为0就等于上下左右中的最小值+1;相当于对之前/之后计算的值做了保存 这里直接用就行
代码:
js
var updateMatrix = function(mat)
for (var i = 0; i <mat.length; i++)
for (var j = 0; j <mat[0].length; j++)
if (mat[i][j] == 1)
mat[i][j] = mat.length + mat[0].length;
if (i > 0)
mat[i][j] = Math.min(mat[i][j], mat[i - 1][j] + 1);
if (j > 0)
mat[i][j] = Math.min(mat[i][j], mat[i][j - 1] + 1);
for (var i = mat.length - 1; i >= 0; i--)
for (var j = mat[0].length- 1; j >= 0; j--)
if (i < mat.length - 1)
mat[i][j] = Math.min(mat[i][j], mat[i + 1][j] + 1);
if (j <mat[0].length - 1)
mat[i][j] = Math.min(mat[i][j], mat[i][j + 1] + 1);
return mat ;
;
DFS
int row = matrix.length;
int col = matrix[0].length;
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
// 优化:如果元素在 0 附近,保留元素值 1,不在 0 附近,初始化为一个较大值
if (matrix[i][j] == 1
&& !((i > 0 && matrix[i - 1][j] == 0)
|| (i < row - 1 && matrix[i + 1][j] == 0)
|| (j > 0 && matrix[i][j - 1] == 0)
|| (j < col - 1 && matrix[i][j + 1] == 0)))
matrix[i][j] = row + col;
System.out.print(matrix[i][j]);
System.out.println();
for (int i = 0; i < matrix.length; i++)
for (int j = 0; j < matrix[0].length; j++)
// 优化:将元素值为 1 的点作为深搜起点,降低递归深度
if (matrix[i][j] == 1)
t_find(matrix, i, j);
return matrix;
private static void t_find(int[][] matrix, int r, int c)
// 搜索上下左右四个方向
int[][] vector = new int[][]0, 1, 0, -1, 1, 0, -1, 0;
for (int[] v : vector)
int nr = r + v[0], nc = c + v[1];
if (nr >= 0 && nr < matrix.length
&& nc >= 0 && nc < matrix[0].length
&& matrix[nr][nc] > matrix[r][c] + 1)
matrix[nr][nc] = matrix[r][c] + 1;//这里也不会错,当下次走到时如果发现是比当前+1大,就更新为当前+1 直至遍历完成
t_find(matrix, nr, nc);
bfs:不进行递归了
public int[][] updateMatrix_2(int[][] matrix)
row = matrix.length;
col = matrix[0].length;
Queue<int[]> queue = new LinkedList<>();
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
if (matrix[i][j] == 0)
// 将所有 0 元素作为 BFS 第一层
queue.add(new int[]i, j);
else
matrix[i][j] = row + col;
while (!queue.isEmpty())
int[] s = queue.poll();
// 搜索上下左右四个方向
for (int[] v : vector)
int r = s[0] + v[0], c = s[1] + v[1];
if (r >= 0 && r < row
&& c >= 0 && c < col
&& matrix[r][c] > matrix[s[0]][s[1]] + 1)
matrix[r][c] = matrix[s[0]][s[1]] + 1;
queue.add(new int[]r, c);
return matrix;
动态规划
19. 994.腐烂的橘子
题目描述:
在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。
思路: 采用DFS:设置初始=0,从头到尾遍历如果是2就递归其上下左右:如果存在一个真实的邻居为1的就count++ (判断他们如果存在且为1)然后就设置为2 不存在就返回0(说明不需要时间本身就烂的) 如果存在是为0,不管;最后如果还有1就返回-1,否则就是 返回count
Tip: 记录新鲜橘子的数量,看看最后是否剩余,剩余的话就返回-1 没有剩余返回次数;腐烂的压入队列,当队列不为空时遍历其上下左右;如果有孩子就次数+1
代码:
class Solution
public int orangesRotting(int[][] grid)
int count_1=0;
Queue<int[] > queue=new LinkedList<int[] >();
int count=0;
for(int i=0;i<grid.length;i++)
for(int j=0;j<grid[0].length;j++)
if(grid[i][j]==1)count_1++;
if(grid[i][j]==2)
queue.add(new int[] i,j);//将所有的腐烂果子都插入进行广度搜索,因此也叫做多源广度搜索
//对整个队列的每一个果子进行上下左右广度搜索
while( !queue.isEmpty())
int n = queue.size();
for (int i = 0; i < n; i++) //每一次压入的这些是并行的 也就是多源是同时增长的
int[] ing=queue.poll();
if( ing[0]-1>=0 && ing[0]-1<grid.length && ing[1]>=0 && ing[1]<grid[0].length && grid[ing[0]-1][ing[1]]==1)
grid[ing[0]-1][ing[1]]=2;
queue.add(new int[] ing[0]-1,ing[1]);
if(ing[0]+1>=0 && ing[0]+1<grid.length && ing[1]>=0 && ing[1]<grid[0].length && grid[ing[0]+1][ing[1]]==1 )
grid[ing[0]+1][ing[1]]=2;
queue.add(new int[] ing[0]+1,ing[1]);
if( ing[0]>=0 && ing[0] <grid.length && ing[1]-1>=0 && ing[1]-1<grid[0].length && grid[ing[0]][ing[1]-1]==1 )
grid[ing[0] ][ing[1]-1]=2;
queue.add(new int[] ing[0] ,ing[1]-1);
if( ing[0] >=0 && ing[0] <grid.length && ing[1]+1>=0 && ing[1]+1<grid[0].length && grid[ing[0]][ing[1]+1]==1 )
grid[ing[0] ][ing[1]+1]=2;
queue.add(new int[] ing[0] ,ing[1]+1);
if (!queue.isEmpty())
count++;//如果是有孩子的,那么需要去计算,否则就不需要时间,不增加
for(int i=0;i<grid.length;i++)//如果最后存在没有腐烂的果子,就返回-1;
for(int j=0;j<grid[0].length;j++)
if(grid[i][j]==1)
return -1;
return count;
20. 21. 合并两个有序链表
题目描述:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路: 如果有一个为空,返回另一个;如果都不为空:值小的选中+其next为剩余里面排好的(对除了选中这个的进行排序)
Tip: 链表不是遍历的,直接next就可以取到或者拼接
代码:
js
var mergeTwoLists = function(list1, list2)
var tmp=new ListNode(-1);
if(list1==null || list2==null)
tmp =list1==null?list2:list1;
elsetmp=list1.val>list2.val?list2:list1;
tmp.next=list1.val>list2.val?mergeTwoLists(list2.next,list1):mergeTwoLists(list1.next,list2);
return tmp;
;
java
class Solution
public ListNode mergeTwoLists(ListNode list1, ListNode list2)
ListNode tmp=new ListNode(-1);
if(list1==null || list2==null)
tmp =list1==null?list2:list1;
elsetmp=list1.val>list2.val?list2:list1;
tmp.next=list1.val>list2.val?mergeTwoLists(list2.next,list1):mergeTwoLists(list1.next,list2);
return tmp;
21. 206反转链表
题目描述:
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
思路: 想着不为空时,倒数第一个+其next为前面的反转后;没有实现的原因是无法让每次的最后一个元素指向空:加一个pre指针也不行
Tip:每次让该元素指向其前一个 元素
递归:先反转后面的;然后head.next.next=head意思是下一个元素的下一个指向该元素,最后head的next指向空
先反转前面的,见代码第三个
代码:
迭代
class Solution
public ListNode reverseList(ListNode head)
if(head==null ) return head;
ListNode cur=head;
ListNode pre=null;
while(cur!=null)
ListNode next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
return pre;
递归
class Solution
public ListNode reverseList(ListNode head)
if(head==null || head.next==null) return head;
ListNode pre= reverseList(head.next);
head.next.next=head;
head.next=null;
return pre;
class Solution
ListNode pre = null, tmp = null;
public ListNode reverseList(ListNode head)
if (head == null)
return pre;
tmp = head.next;
head.next = pre;
pre = head;
head = tmp;
return reverseList(head);
22.77.组合
题目描述:
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
思路: 对于当前元素,如果选择了就在剩下的元素里选k-1个;如果没选择当前元素,就在剩下的里面选择k个;终止条件是只有一个元素或者全部选择这些元素
Tip:回溯+剪枝
代码:
结果不对:输出的结果不对,思路没问题
class Solution
public List<List<Integer>> combine(int n, int k)
return combine2(n,k,1);
public List<List<Integer>> combine2(int n, int k,int startIndex)
List<Integer> list1=new LinkedList<Integer>();
List<List<Integer>> list=new ArrayList<List<Integer>>();
if(k==1)
for(int i=startIndex;i<=n;i++)
list1.add(i);
list.add(list1);
return list;
if(k==(n-startIndex+1))
for(int i=startIndex;i<=n;i++)
list1.add(i);
list.add(list1);
return list;
for(int j=0;j<combine2( n - 1, k - 1,startIndex+1).size() ;j++)
List<Integer> c= combine2( n - 1, k - 1,startIndex+1).get(j) ;
c.add(n);
list.add(c);
for(int j=0;j<combine2(n, k, startIndex+1).size() ;j++)
list.add(combine2( n - 1, k - 1,startIndex+1).get(j));
return list;
class Solution
List<List<Integer>> list=new ArrayList<List<Integer>>();
public List<List<Integer>> combine(int n, int k)
combine_all(n,k,1,new LinkedList<Integer>());//每一次k个节点遍历完之后 下一个list1会清空
return list;
private void combine_all(int n, int k, int start, List<Integer> list1)
if(k==0)list.add(new LinkedList<Integer>(list1));return;
for(int i=start;i<=n-k+1;i++)//剪枝
//这里就有剪枝:首先是遍历过的树不再遍历,其次是小于k的不进行回溯,因为倒数小于k的子树不需遍历 他们不会单独构成结果
list1.add(i);//当前节点添加进去
combine_all(n, k-1, i+1,list1);//以2为节点的后面找k-1个 直到移走2后才会移走1来选择以2开头的树
//中途有
list1.remove(list1.size()-1);//找到以后进行回溯,不包含这个节点的情况 不选1 从2开始
23. 46.全排列
题目描述:
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
思路: 采用回溯,遇到自己时remove掉,这样需要开辟一个临时数组进行当前根节点的保存以免重复访问
Tip: 一定要深拷贝 一定要回溯 采用访问标记就行,和重新开辟数组的空间差不多
代码:
class Solution
List<List<Integer>> list=new ArrayList<List<Integer>>();//定义成全局的会少传些参数
List<Integer> tmp=new ArrayList<Integer>();
public List<List<Integer>> permute(int[] nums)
int visited[]=new int[nums.length];
dfs_permute(nums,visited);
return list;
private void dfs_permute(int[] nums, int[] visited)
//终止条件
if(tmp.size()==nums.length)
//记住这里必须使用深拷贝,否则输出的是空list.add(tmp);
list.add(new ArrayList<Integer>(tmp));
return;
//处理
for(int i=0;i<nums.length;i++)//树的宽度
if(visited[i]==1) continue;
visited[i]=1;
tmp.add(nums[i]);
dfs_permute(nums,visited);
visited[i]=0;
//回溯
tmp.remove(tmp.size()-1);
24. 784. 字母大小写全排列
题目描述:
给定一个字符串 s ,通过将字符串 s 中的每个字母转变大小写,我们可以获得一个新的字符串。
返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。
思路: 回溯:是数字直接添加,是大写先转小写再回溯,是小写先转大写再回溯
Tip: 如果遇到数字就跳出,如果在数字中添加会造成重复添加
或者每遍历一个就添加一遍,如果是大写就转小写再添加一遍,是小写就转大写再添加一遍
代码:
class Solution
List<String> list=new ArrayList<String>();//定义成全局的会少传些参数
public List<String> letterCasePermutation(String s)
char[] ch=s.toCharArray();
dfs_letterCase(ch,ch.length,0);
return list;
private void dfs_letterCase(char[] ch, int s_length, int index)
list.add(new String(ch));
if(index>=ch.length)
return;
for(int i=index;i<s_length;i++)
// if(Character.isDigit(ch[i])) dfs_letterCase(ch,s_length,i +1); 这里会造成重复添加
if(Character.isDigit(ch[i])) continue;
else if(Character.isLowerCase(ch[i]))
//转为大写后 继续下一个 回溯
ch[i]=Character.toUpperCase(ch[i]);
dfs_letterCase(ch,s_length,i +1);
ch[i]=Character.toLowerCase (ch[i]);
else
//继续下一个
//转为小写后 继续下一个
ch[i]=Character.toLowerCase (ch[i]);
dfs_letterCase(ch,s_length,i +1);
ch[i]=Character.toUpperCase(ch[i]);
25. 70.爬楼梯
题目描述:
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路: 方法学习了回溯,硬是想到当前走一步或者走两步就行,但走一步后怎么回溯?
后来看题解了解了动态规划:当前的走法等于上一个台阶的走法+上两个台阶的走法
Tip: 也可以将每一步的都保存在数组中,这样该步
以上是关于leecode+剑指offer的主要内容,如果未能解决你的问题,请参考以下文章