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;
}
else{tmp=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;
}
else{tmp=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: 也可以将每一步的都保存在数组中,这样该步就等于前两个 空间为o(n)
代码:
class Solution {
public int climbStairs(int n) {
if(n==1 || n==2) return n;
return climbStairs(n-1)+climbStairs(n-2);
}
}
报错超出了时间限制
那就不使用递归,直接手动添加
保存当前节点前两个的值,进行遍历
class Solution {
public int climbStairs(int n) {
int count=0;
if(n==1 ) return 1;
if(n==2 ) return 2;
int a=1,b=2;
for(int i=3;i<=n;i++){
int tmp=a;
a=b;
b=tmp+a;
}
return b;
}
}
26. 198.打家劫舍
题目描述: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路: 动态规划
- 数组下标含义:代表有i家房屋的最大值
- 递推公式:dp[i]=max(dp[i]-num[i-1]+num[i],dp[i-1]),当前最大值为选择当前或者不选当前的最大值,如果选了当前就需要删除选择的前一个。改进:dp[i]= max(dp[i-1],dp[i-2] +nums[i-1]);这里i-2是因为dp数组的长度比nums多了1
- 初始化:dp[0]=1.dp[1]=num[1]
- 遍历顺序:从前向后
- 打印数组
Tip: 有点问题:递推公式应该是上两步处理结果+该房屋的钱,跳过第i-1个房屋----因为 他也不一定选了,直接减去会导致多减
代码:
class Solution {
public int rob(int[] nums) {
int[] dp=new int[nums.length+1];
if(nums.length==0) return 0;
if(nums.length==1) return nums[0];
dp[0]=0;
dp[1]=nums[0];
dp[2]=Math.max(nums[0],nums[1]);
for(int i=3;i<=nums.length;i++){
dp[i]=Math.max(dp[i-1],dp[i-1]-nums[i-2]+nums[i-1]);改为 dp[i]=Math.max(dp[i-1],dp[i-2] +nums[i-1]);
}
return dp[nums.length];
}
}
27. 120. 三角形最小路径和
题目描述:
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
思路: 从上到下动态规划 每一个点保存当前位置的最小值------从上到下也行吧?
Tip: 从下到上 得到每一个位置的最小值,这样到了最上面才可以看到最小值
注意:这里从上到下的话需要先初始化第一列与前两行,以及最后一行
代码:
从后向前
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
if(triangle.size()==1) return Collections.max(triangle.get(0));
int[][] dp=new int[triangle.size()+1][triangle.get(triangle.size()-1).size()+1];
for (int i = triangle.size() - 1; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
当前最小的等于当前值加上下一层邻居中最小的,如果从上到下 就是当前值加上上面两个中最小的那一个,到了最后选择。。。
dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
}
}
return dp[0][0];
}
}
从前向后
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
if(triangle.size()==1) return Collections.min(triangle.get(0));
int[][] dp=new int[triangle.size() ][triangle.get(triangle.size()-1).size()];
dp[0][0]=triangle.get(0).get(0);
dp[1][1]=dp[0][0]+triangle.get(1).get(1);
for (int i =1;i< triangle.size() ;i++ ){
dp[i][0]=dp[i-1][0]+triangle.get(i).get(0);
}
for (int i =1;i< triangle.size() ;i++ ){
dp[i][triangle.get(i).size()-1]=dp[i-1][triangle.get(i-1).size()-1]+triangle.get(i).get(triangle.get(i).size()-1);
}
for (int i =2;i< triangle.size() ;i++) {
for (int j = 1; j <triangle.get(i).size()-1; j++) {
dp[i][j] = Math.min(dp[i -1][j-1], dp[i -1][j]) + triangle.get(i).get(j);
}
}
return Arrays.stream(dp[ triangle.size()-1 ]).min().getAsInt();最后一行中的最小值
}
}
28. 231.2.2的幂
题目描述:
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/power-of-two
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路: 一直除以二,如果余数不为零且n!=1就返回false,其他余数不为0且n<1就为false
Tip:
- 位运算是指n与n-1与操作为0 就行: n & n-1可以把n最低位的1变0,而当n & n-1 == 0时,则说明n只有一个1。
- n & (-n)结果为n由于负数是按照补码规则在计算机中存储的
代码:
class Solution {
public boolean isPowerOfTwo(int n) {
if(n<1) return false;
if(n==1) return true;
return (n& n-1)==0?true:false;
}
}
class Solution {
public boolean isPowerOfTwo(int n) {
if(n<1) return false;
while(n%2==0){
n=n/2;
}
if(n==1) return true;
else return false;
}
}
29. 191. 位1的个数
题目描述:
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。
思路:
- 方法长度固定32,直接遍历计算为1的个数
- 遍历32次,每次舍弃低位并检查低位是1还是0 采用逻辑右移的思想
Tip:
- java中逻辑右移与算数右移
- n&n-1会消掉最末尾的那个1 当只有一个1时说明是2的幂次 消1次就变为0了
- 除以2取余时需要考虑是==1还是-1 因为负数取余是-1
代码:
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count=0;
while(n!=0){
count+=n&1;//n&1每次判断最后一位是0还是1 或者直接除2来判断
n>>=1;
}
return count;
}
}
这两种都超时了:那么对于负数而言,其二进制最高位是 1,如果使用算术右移,那么高位填补的仍然是 1。也就是 n 永远不会为 0。所以 代码会超时 TLE。
应该使用逻辑右移>>>
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count=0;
while(n!=0 ){
System.out.println(-3%2 );
if(n%2==1 ||n%2==-1) count++;//count+=n%2==1?1:0;//每次判断最后一位是0还是1 或者直接除2来判断
n>>>=1;
}
return count;
}
}
30. 190. 颠倒二进制位
题目描述:
颠倒给定的 32 位无符号整数的二进制位。
思路: 最笨的方法就是循环16次反转,然后转成int型
Tip:
- 1.就是和上面思路一样,需要用位运算代替字符串的那些方法
- 2.分治:每次对半交换 ,对每一半左移8位右移8位.....直到最后为1就拍好了 是奇数偶数位交换哦
- 3.| 代表拼接的意思
代码:
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int res=0;
for(int i=0;i<32;i++){
res=res+((1&(n>>>i))<<(31-i));//首先1&n是获取n的最后一位,每次获取第i步的最后一位 (1&(n>>>i)相当于charAt(31-i)
//获取之后就需要进行拼接:当前获取了1位 过去已经获取了i位 一共32位因此需要当前的1位在其右边补0 也就是左移32-1-i位
}
return res;
}
}
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int res=0;
for(int i=0;i<32;i++){
res=res | ((1&(n>>>i))<<(31-i));//首先1&n是获取n的最后一位,每次获取第i步的最后一位 (1&(n>>>i)相当于charAt(31-i)
//获取之后就需要进行拼接:当前获取了1位 过去已经获取了i位 一共32位因此需要当前的1位在其右边补0 也就是左移32-1-i位
}
return res;
}
}
31. 136. 只出现一次的数字
题目描述:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
思路: 刚开始想着是异或,但是以为是遍历异或不为0的,后来不对
Tip: 因为两个相同的数异或是0,所以当这一堆数字异或后,相同的那些都为0了,再异或那个一个的数字就是该数字本身
代码:
class Solution {
public int singleNumber(int[] nums) {
int res=0;
for(int i=0;i<nums.length;i++){
res=nums[i]^res;
}
return res;
}
}
二、剑指offer
1. JZ6 从尾到头打印链表
题目描述:
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
思路: 对链表进行反转,保存到数组中
Tip:
1.先读取到数组中,然后进行数组反转
2.还有一种递归方法
java 递归超简洁版本
public class Solution {
ArrayList<Integer> arrayList=new ArrayList<Integer>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode!=null){
this.printListFromTailToHead(listNode.next);
arrayList.add(listNode.val);
}
return arrayList;
}
}
代码:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> res=new ArrayList<Integer>();
if(listNode==null) return res;
if(listNode.next==null){ res.add(listNode.val);
return res;
}
//得到一个逆序的链表
ListNode pre=null,tmp=listNode;
while(listNode!=null){
tmp=listNode.next;
listNode.next=pre;
pre=listNode;
listNode=tmp;
}
//遍历输出值
while(pre!=null){
res.add(pre.val);
pre=pre.next;
}
return res;
}
}
2 . JZ24 反转链表
题目描述:
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
思路:之前有递归方法 迭代方法,这里采用栈进行
Tip: 最后的node.next=null一定要加,不加通不过 但不明白为什么会构成环,对了应该是因为该节点就是head吧
代码:
import java.util.*;
public class Solution {
public ListNode ReverseList(ListNode head) {
Stack<ListNode> stack= new Stack<>();
//把链表节点全部摘掉放到栈中
while (head != null) {
stack.push(head);
head = head.next;
}
if (stack.isEmpty())
return null;
ListNode node = stack.pop();//node。next保存每一次出栈的节点,node便于定位位置
ListNode res=node;
//栈中的结点全部出栈,然后重新连成一个新的链表
while (!stack.isEmpty()) {
node.next = stack.pop();
node=node.next;
}
//最后一个结点就是反转前的头结点,一定要让他的next
//等于空,否则会构成环
node.next=null;
return res;
}
}
3. JZ25 合并两个排序的链表
题目描述:
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
思路: 方法如果有一个为空,返回另一个;如果都不为空,返回小的+剩余后边拍好序的列表
Tip:
代码:
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null || list2==null) {list1=list1==null?list2:list1; return list1;}
else{
if(list1.val>list2.val){list2.next=Merge(list1,list2.next); return list2;}
else{list1.next=Merge(list1.next,list2); return list1;}
}
}
}
4.JZ52 两个链表的第一个公共结点
题目描述:
输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
思路: 方法判断如果两个都不为空时,找到相同的返回
还有一种就是让长的先走,因为他们后面相同:当遇到第一个相同的就行,测试数据都是这样的,即确保长度相同的情况下第一个值相同后面就都相同
Tip: 这样会忽略一种情况,就是找到了相同的节点,但他们的长度不一样怎么办
- 1.如果一个为空后,再遍历另一个链表,这样每个指针会走l1+l2的长度 因此必然会相遇
- 2.记录长度:每走一个长度就-1;如果值相等但长度不等就长的继续减 直到为空 如果都相等直接返回
- 3.官方使用集合的方法不对,犯了和自己思路一样的错误:得到两个都包含的元素
- 4.使得两个同时开始:长的先走走到剩余的与短的相等 然后同时开始
总之:一个值可以重复多次,链表中值不是递增的
总结:可以使每个指针走两个链表,找相遇的节点;可以直接比较最后一个节点;可以让长度长的先走k
public class Solution {
public ListNode FindFirstCommonNode(ListNode p1, ListNode p2) {
ListNode pHead1=p1;
ListNode pHead2=p2;//这里不写就会报错空指针异常
while(pHead1 !=pHead2){
pHead1=pHead1==null?p2:pHead1.next;
pHead2=pHead2==null?p1:pHead2.next;
}return pHead1;
}
}
public class Solution {
public ListNode FindFirstCommonNode(ListNode p1, ListNode p2) {
ListNode pHead1=p1;
ListNode pHead2=p2;
int l1=0,l2=0;//计算长度
while(pHead1!=null){
l1++;
pHead1=pHead1.next;}
while(pHead2!=null){
l2++;
pHead2=pHead2.next;}
pHead1=p1;//同步开始
pHead2=p2;
if(l1>l2) {
for(int i=0;i<l1-l2;i++) pHead1=pHead1.next;
}
else{
for(int i=0;i<l2-l1;i++) pHead2=pHead2.next;
}
while(pHead1!=pHead2){
pHead1=pHead1.next;
pHead2=pHead2.next;
} return pHead1;
}
}
代码:
思路失败1
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
while(pHead1!=null && pHead2!=null){
if(pHead1.val==pHead2.val) return pHead1;
else if(pHead2.val>pHead1.val) pHead1=pHead1.next;
else pHead2=pHead2.next;
}
return pHead1;
}
}
先走失败2
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
int l1=0,l2=0;
while(pHead1!=null){
l1++;
pHead1=pHead1.next;}
while(pHead2!=null){
l2++;
pHead2=pHead2.next;}
if(l1>l2) {
for(int i=0;i<l1-l2;i++) pHead1=pHead1.next;
}
else{
{
for(int i=0;i<l2-l1;i++) pHead2=pHead2.next;
}
}
while(pHead1!=null && pHead2!=null){
if(pHead1.val==pHead2.val) {
//长度不等时 长的那个继续往前走
return pHead1;}
pHead1=pHead1.next;
pHead2=pHead2.next;
} return pHead1;
}
}
5. JZ23 链表中环的入口结点
题目描述:
给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
思路: 方法从头开始遍历每一个节点:如果节点的next不为空,就看看是否有同时指向他的节点;找到就返回否则返回空
Tip: 定义一个集合,按顺序添加,如果最后有重复的 就是有环
定义两个指针,一个快的一个慢的,如果有环这两个指针肯定会相遇;那么通过如下式子可以知道,相遇点到环的入口的距离等于点到环入口的距离
2(a+b)=a+k(b+c)+b
a+b=k(b+c)因此得到a=(k-1)(b+c)+c;我们如果能得到相遇点:然后一个指针从头开始 一个指针从相遇点开始 他们相遇时就是所求点
import java.util.*;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
ListNode p1=pHead;
ListNode p2=pHead;
while(p2!=null && p2.next!=null){ //这里一定要注意,看快指针就行,当慢指针为空的时候快指针不为空也可以判定是有环,这里主要 考虑快指针为空时慢指针不一定为空 但可以知道其没环
p1=p1.next;
p2=p2.next.next;
if(p1==p2) break;
}
if(p2==null || p2.next==null) return null;
p2=pHead;
while(p1!=p2){
p1=p1.next;
p2=p2.next;
}
return p2;
}
}
代码:
import java.util.*;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
//定义一个set,如果元素已经在集合中,说明他是环的入口
// if(pHead==null) return null;
HashSet<ListNode> hs=new HashSet<ListNode>();
while(pHead!=null){
if(!hs.add(pHead)) return pHead;
pHead=pHead.next;
}
return null;
}
}
6. JZ22 链表中倒数最后k个结点
题目描述: 输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。如果该链表长度小于k,请返回一个长度为 0 的链表。
思路: 设置两个指针,循环第一个不为空时 第一个先走k步,如果还没有走k-1步就为空了那就返回空,否则会继续 然后第一个指针从0开始,第二个指针继续next 当第二个next指向空时,第一个
Tip:
定如果k<=0或者链表为空 直接返回空;如果k==1直接走到最后返回最后一个;对于其他情况就需要双指针判断了
快慢两个指针中间间隔是k-1哦,但是没所谓当节点为空时上一个已经向后走了一部:意思是指向空的这一步慢指针向前走了一步
代码:
public ListNode FindKthToTail (ListNode pHead, int k) {
if(k==0) return null;//这里条件也可以写成<=
ListNode p2=pHead;
while(k>1 ){//第二个指针先走k步 如果k=1就不用先走
if(p2==null ) return null;
if( p2.next==null) return null;
p2=p2.next;
k--;
}
if(p2.next==null) return pHead;//说明k为链表长度
while(p2.next !=null){
pHead=pHead.next;
p2=p2.next;
}
return pHead;
}
}
//k==1的话只需要一个指针就行,看看这样的效率
import java.util.*;
public class Solution {
pu if(k==0) return null;
ListNode p2=pHead;
//k==1的话只需要一个指针就行,看看这样的效率 这样时空复杂度更大
if(k==1){
while(pHead.next !=null){
pHead=pHead.next;
}
return pHead;
}
while(k>1 ){//第二个指针先走k步
if(p2==null ) return null;
if( p2.next==null) return null;
p2=p2.next;
k--;
}
if(p2.next==null) return pHead;
while(p2.next !=null){
pHead=pHead.next;
p2=p2.next;
}
return pHead;
}
}
7. JZ35 复杂链表的复制
题目描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
思路:
- 直接遍历复制:暴力法进行复制,对于next就是直接new一个旧的然后给予新的next;对于random就是从头到尾判断该值是否等于当前旧节点的random,等于就赋予新的,没有就就下一个
- 递归:需要注意java中的深拷贝
- 哈希表:首先遍历一遍原链表,创建新链表(赋值label和next),用map关联对应结点:新 旧;再遍历一遍,更新新链表的random指针。(注意map中应有NULL----> NULL的映射)
Tip:指针复制
拆分的时候主要是使得除头节点外其余next变为next.next 返回,也就是跳过一条
复制指针
if (pHead == null) {
return null;
}
//复制每个节点并插入到原节点后面
RandomListNode curr = pHead;
while (curr != null) {
RandomListNode clone = new RandomListNode(curr.label);
clone.next = curr.next;
curr.next = clone;
curr = clone.next;
}
//处理random的指向
curr = pHead;
while (curr != null) {
if (curr.random != null) {
curr.next.random = curr.random.next;
}
curr = curr.next.next;
}
//最后一步,拆分链表 a a1 b b1 c c1
curr = pHead;
RandomListNode pHeadClone = curr.next;
RandomListNode temp;
while (curr.next != null) {
temp = curr.next;
curr.next = temp.next;
curr = temp;
}
return pHeadClone;
Hashmap
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
import java.util.*;
public class Solution {
RandomListNode head = new RandomListNode(0);
HashMap<RandomListNode,RandomListNode> map = new HashMap<RandomListNode,RandomListNode>();
public RandomListNode Clone(RandomListNode pHead)
{
RandomListNode p = head;
RandomListNode old_pHead = pHead;
while(pHead != null){
RandomListNode t = new RandomListNode(pHead.label);;
map.put(pHead,t);
p.next = t;
p = t;
pHead = pHead.next;
}
p = head.next;
while(old_pHead != null){
p.random = map.get(old_pHead.random);
old_pHead = old_pHead.next;
p = p.next;
}
return head.next;
}
}
暴力法
import java.util.*;
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{ if (pHead == null) return null;
RandomListNode newNode = new RandomListNode(0);//新的,有一个空指针
//如果需要复制,就设置两个指针,一个指向旧的,一个指向新的
//其中原先头指针是不能动的,
RandomListNode newP=newNode;
RandomListNode oldP=pHead;
while(oldP !=null){
newP.next=new RandomListNode(oldP.label);//因为有空指针,所以比原先先走一步 即该步是复制原先的上一步的
newP=newP.next;
oldP=oldP.next;
}
//现在已经得到了一个不包含random的链表
RandomListNode newRandom= newNode.next;//newNode是包含头节点为空的节点
RandomListNode oldRandom=pHead;
while(oldRandom!=null){
//从头到尾找该oldrandom的random
RandomListNode tmp_oldRandom=pHead;
RandomListNode tmp_newRandom=newNode.next;
while(tmp_oldRandom !=oldRandom.random){//找当前节点的random在新的中指向哪里,因为旧的只指向旧的,这里需要多一个指针判断,使他们同步走就行
tmp_newRandom=tmp_newRandom.next;
tmp_oldRandom=tmp_oldRandom.next;
}
newRandom.random=tmp_newRandom;
newRandom=newRandom.next;
oldRandom=oldRandom.next;
}
return newNode.next;
}
}
代码:
意思是只有拷贝了最后一次的值,没有进行深拷贝 传参这有点问题
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
RandomListNode rd=new RandomListNode(pHead.label);
if(pHead==null) return null;
while(pHead!=null){
rd.next=pHead.next;
rd.random=pHead.random;
pHead=pHead.next;
}
return rd;//这样不对,rd只能返回第一个值
}
}
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{ if (pHead == null) return null;
RandomListNode newNode = new RandomListNode(pHead.label);
newNode.random = pHead.random;
newNode.next = Clone(pHead.next);
return new RandomListNode(newNode.label);
}
} 也不对
但是 这个可以
class Solution:
# 返回 RandomListNode
def Clone(self, head):
# write code here
if not head: return
newNode = RandomListNode(head.label)
newNode.random = head.random
newNode.next = self.Clone(head.next)
return newNode
8. JZ76 删除链表中重复的结点
题目描述:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5
思路: 需要在前面加一个头节点,刚开始审错题了,以为只要把重复的留下一次就行,那那么简单,所以审题很重要
想着接着在原先基础上,将每一次的重复值保存到map中 然后最后遍历一遍链表将出现在map中的删掉就行 但是这样超时了
Tip:
1.采用递归的方法:每一次都只处理一个元素,如果该元素与其下一个是重复的,就找到直到不重复的那一个进行递归;如果不是重复的,保留该节点,其next为后面剩下处理完的结果 即递归的结果 这种方法每次会处理一种值相等的元素
2.采用指针的方法:定义一个pre指针,确保pre总是在当前指针的前一个,这样就可以pre.next指向那个不等于该节点的指针了 这里注意判断该节点等于其next才循环,所以循环结束的时候只是判断不相等了,并没有赋值,因此对于重复的pre.next要指向tmp.next 这样pre指向的就是另一种元素的前一个节点,也就是这种元素的最后一个节点
代码:
递归
public class Solution {
public ListNode deleteDuplication(ListNode p) {
if(p==null || p.next==null) return p;
if(p.val==p.next.val) {
ListNode tmp=p.next;//保存tmp的最后一个,tmp的next就不是重复的了
while(tmp!=null && tmp.val==p.val){
tmp=tmp.next;
}
//找完以后就不管该节点了,直接去他的next里 也就是不能于他的那个值里去处理
return deleteDuplication(tmp );
}
else{//当前这个值不重复,那么最后需要返回这个值
p.next=deleteDuplication( p.next);
return p;
}
}
}
指针
public class Solution {
public ListNode deleteDuplication(ListNode p) {
if(p==null || p.next==null) return p;
ListNode pre=new ListNode(0);//定义一个头结点,便于返回
pre.next=p;
ListNode tmp_pre= pre;//为了去跟随当前指针变化
while(p!=null){
if(p.next!=null && p.val==p.next.val){
while(p.next!=null && p.val==p.next.val){
p=p.next;
}
tmp_pre.next=p.next;//因为其下一个不等于其自己的时候就跳出了 所以循环内其下一个还等于本身,所以这里要next
p=p.next;
}
else{
tmp_pre=tmp_pre.next;
p=p.next;
}
}
return pre.next;
}
}
9. JJZ18 删除链表的节点
题目描述:
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
1.此题对比原题有改动
2.题目保证链表中节点的值互不相同
3.该题只会输出返回的链表和结果做对比,所以若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
思路: 方如果链表为空,返回空;否则就定义一个pre指针以及一个当前指针,从前往后如果遇到值相等的就返回;如果没有遇到就返回最原始的链表 因此head是不可以进行变化处理的
Tip: 定
代码:
public ListNode deleteNode (ListNode head, int val) {
// write code here
if(head==null) return null;
ListNode pre=new ListNode(0);
pre.next=head;
ListNode tmp_pre=pre;
ListNode cru=pre.next;
System.out.println(cru.val);
while(cru!=null){
if(cru.val==val) {tmp_pre.next=cru.next;
return pre.next;}
tmp_pre=tmp_pre.next;
cru=cru.next;
}
return head;
}