代码随想录

复杂度、测试用例、边界条件

时间复杂度:看每一个元素被操作的次数

 图解!24张图彻底弄懂九大常见数据结构!_业余码农AmazingJ的博客-CSDN博客_图解数据结构

 

void()仅仅是代表不返回任何值,但是括号内的表达式还是要运行,如

void(alert("Warnning!"))

1 数组

理论:

数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

例1 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

方法 双指针法

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

数组、链表、字符串常见使用双指针法。

思路

慢指针重新定义一个数组,快指针遍历原数组,若是目标元素,就不赋值给新数组,非目标元素,赋值 

```javascript
var removeElement = function(nums, val) {
   let k=0
   for(let i=0;i<nums.length;i++){
       if (nums[i]!= val){
           nums[k++]=nums[i]
      }
  }
   return k
};

例2 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

方法 双指针法

思路

原数组和新数组都是递增排序,原数组左右端各一个指针,所指的元素平方比较大小,大的赋值给新数组最右端

var sortedSquares = function(nums) {
   let i=0
   let j=nums.length-1
   let k=nums.length-1
   let res = new Array(nums.length-1).fill(0)
   while(k>=0){
       if(nums[i]*nums[i]<nums[j]*nums[j]){
           res[k--]=nums[j]*nums[j]
           j--
      }else {
           res[k--]=nums[i]*nums[i]
           i++
      }
  }
   return res
};

例3 长度最小的子数组

方法 滑动窗口

就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。

思路

两个循环 第一个循环是右指针遍历数组,累加和;第二个循环是用来窗口滑动:确定和是否大于目标值,同时移动左指针,这里注意计算子数组的长度r-l(因为上一个循环r已经多加1)

 

var minSubArrayLen = function(target, nums) {
   // 长度计算一次
   const len = nums.length;
   let l = r = sum = 0,
       res = len + 1; // 子数组最大不会超过自身
   while(r <= len-1) {   //r=len时取不到
       sum += nums[r++];
       // 窗口滑动
       while(sum >= target) {
           // r始终为开区间 [l, r)
           res = res < r - l ? res : r - l;
           sum-=nums[l++];
      }
  }
   return res > len ? 0 : res;
};

例4 螺旋矩阵Ⅱ

给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

for (; col < n; col++) {   }    //在循环开始前已经设置了col值

思路:

确定转多少圈(loop),每一圈遍历的起始位置startX (0,0)→(1,1)→(2,2)…,中间位置mid是为了当n为奇数时最后一个数,offset 用于表达每一次遍历的条件。循环是loop不断减一

var generateMatrix = function(n) {
   let startX = startY = 0;   // 起始位置
   let loop = Math.floor(n/2);   // 旋转圈数
   let mid = Math.floor(n/2);    // 中间位置
   let offset = 1;    // 控制每一层填充元素个数
   let count = 1;     // 更新填充数字
   let res = new Array(n).fill(0).map(() => new Array(n).fill(0));

   while (loop--) {
       let row = startX, col = startY;
       // 上行从左到右(左闭右开)
       for (; col < startY + n - offset; col++) {
           res[row][col] = count++;
      }
       // 右列从上到下(左闭右开)
       for (; row < startX + n - offset; row++) {
           res[row][col] = count++;
      }
       // 下行从右到左(左闭右开)
       for (; col > startX; col--) {
           res[row][col] = count++;
      }
       // 左列做下到上(左闭右开)
       for (; row > startY; row--) {
           res[row][col] = count++;
      }

       // 更新起始位置
       startX++;
       startY++;

       // 更新offset
       offset += 2;
  }
   // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
   if (n % 2 === 1) {
       res[mid][mid] = count;
  }
   return res;
};

2 链表

定义

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链接的入口节点称为链表的头结点也就是head。

class ListNode {
 val;
 next = null;
 constructor(value) {
   this.val = value;
   this.next = null;
}
}

分类

单链表

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。双链表 既可以向前查询也可以向后查询。

循环链表:链表首尾相连。解决约瑟夫环问题。

 

 

链表的存储方式

数组是在内存中是连续分布的,但是链表在内存中不是连续分布的。

链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

例1 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

思路

设置一个虚拟节点ret,这样操作所有链表节点都会以同样的逻辑。再定义一个暂时节点cur遍历链表。

if: 判断当前节点下一个节点的数据值是否等于目标值,是的话,指针指向下一个的下一个。再返回循环中,判断下一个节点的数据值是否等于目标值,不等的话,cur遍历到下一个节点

/**
* Definition for singly-linked list.
* function ListNode(val, next) {
*     this.val = (val===undefined ? 0 : val)
*     this.next = (next===undefined ? null : next)
* }
*/
var removeElements = function(head, val) {
   let res=new ListNode(0,head)
   let cur=res
   while(cur.next){
       if (cur.next.val==val){
           cur.next=cur.next.next
           continue
      }
       cur=cur.next
  }
   return res.next
};
 

例2 设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。

  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。

  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。

  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。

  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

思路

考虑在链表的什么位置操作,注意头尾分情况考虑。size数量变化,最重要的是index考虑头尾节点的变化,操作该节点是否影响head、tail变化。注意在合适处加上return 否则报错

①先定义一个类LinkNode-链表 ②初始化数据结构(单链表储存头尾节点和节点数量)

Get函数:③定义原型方法 获得节点getNode (index在[0,size-1]之外,返回null;创建虚拟头节点cur,★利用while循环,index递减,使cur遍历到初始index处) ④ 返回利用getNode返回该index节点的val

addAtHead 函数 ⑤定义一个新节点node,分别重新定义head、size、tail,若tail不存在,说明原链表不存在,将node再赋值给tail

addAtTail 函数 ⑥ 定义一个新节点node,size++,判断tail是否存在(原链表是否为null),是()否(node赋值给头尾)

addAtIndex 函数 ⑦

    • index > this._size return

    • index <= 0 addAtHead

    • index === this._size addAtTail

  • 0<index<size 利用getNode 获取目标节点的上一个节点node,定义一个新节点赋值给node.next

deleteAtIndex 函数 ⑧

  • index < 0 || index >= size

  • index === 0

    • index === this._size - 1 //如果删除的这个节点同时是尾节点,要处理尾节点

  • 0<index<size 获取目标节点的上一个的节点

    • index === this._size - 1

class LinkNode {
   constructor(val, next) {
       this.val = val;
       this.next = next;
  }
}
var MyLinkedList = function() {
   this._size=0
   this._head=null
   this._tail=null
};

MyLinkedList.prototype.getNode = function(index) {
   if(index<0||index>=this._size) return null
   let cur = new LinkNode(0,this._head)
   while(index-- >=0){
       cur=cur.next
  }
   return cur
};

MyLinkedList.prototype.get = function(index) {
   if(index<0||index>=this._size) return -1
   return this.getNode(index).val
};

MyLinkedList.prototype.addAtHead = function(val) {
   let node = new LinkNode(val,this._head)
   this._head = node
   this._size++
   if(!this._tail){
       this._tail=node
  }
};

MyLinkedList.prototype.addAtTail = function(val) {
   let node = new LinkNode(val,null)
   this._size++
   if(this._tail){
       this._tail.next=node
       this._tail=node
       return
  }
   this._tail=node
   this._head=node    
};

MyLinkedList.prototype.addAtIndex = function(index, val) {
   if (index>this._size) return
   if (index<=0){
       this.addAtHead(val)
       return
  }
   if(index==this._size){
       this.addAtTail(val)
       return
  }
   let node=this.getNode(index-1)
   node.next=new LinkNode(val,node.next)
   this._size++
};

MyLinkedList.prototype.deleteAtIndex = function(index) {
   if(index < 0 || index >= this._size) return;
   if(index === 0) {
       this._head = this._head.next;
       // 如果删除的这个节点同时是尾节点,要处理尾节点
       if(index === this._size - 1){
           this._tail = this._head
      }
       this._size--;
       return;
  }
   // 获取目标节点的上一个的节点
   const node = this.getNode(index - 1);    
   node.next = node.next.next;
   // 处理尾节点
   if(index === this._size - 1) {
       this._tail = node;
  }
   this._size--;
};

例3 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

var swapPairs = function(head) {  
   let ret = new ListNode(0, head), temp=ret
   while(temp.next && temp.next.next){
       let pre = temp.next, cur=temp.next.next        
       temp.next=cur        
       pre.next=cur.next
       cur.next=pre       // 这一步得最后 why?否则超出时间限制 节点顺序讲究?否则(pre.next=pre)
       
       temp=pre
  }
   return ret.next
};

例四 删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

法1 暴力求解

-空间复杂度:O(1) 常数量级的额外空间

-时间复杂度:O(n)

  • 找到链表复杂度O(n)

  • 删除从列表开头数起第(L-n+1)个节点:O(n)

法2 快慢指针

-关键字:倒数第N个

-模式识别

  • 涉及链表的特殊位置,考虑快慢指针

  • 要删除链表节点,找到它的前驱

快的要比慢的多n+1步

 
思路
  • 遍历链表节点最常用的方法

    • while(n--) fast = fast.next; //fast 移动n步

    • while(n-->=0) fast = fast.next; //fast 移动n+1步

var removeNthFromEnd = function(head, n) {
   let ret = new ListNode(0, head),
       slow = fast = ret;
   while(n--) fast = fast.next; // 1.快指针先移动n步
   while (fast.next !== null) {
       fast = fast.next;
       slow = slow.next
  }; // 2.快慢指针同时移动,直到快到next,即慢在目标节点的上一节点
   slow.next = slow.next.next; //3. 删除目标节点
   return ret.next;
};
 

 三数之和

 

 

 

 

 

 

 

 

posted @ 2022-04-30 23:57  衣囧~  阅读(48)  评论(0编辑  收藏  举报