leetcode题目

数组

基础知识

  • 数组是存放在连续内存空间上的相同类型数据的集合。
  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的
  • 因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址;例如:删除下标为3的元素,需要对下标为3的元素后面的所有元素都要做移动操作
  • C++,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
  • 数组的元素是不能删的,只能覆盖。

二分查找

二分法:

O(logn)

  • 二分法的前提条件: 数组为有序数组数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
  • 循环不变量规则:区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while循环寻找中每一次边界的处理都要坚持根据区间的定义不变量)来操作,这就是循环不变量规则。(左闭右开/左开右闭)
  • 二分法区间的定义:一般为两种,左闭右闭即[left, right]左闭右开即[left, right)
 [left, right]:
            left = 0, right = nums.length - 1while (left <= right) 要使用 <=if (nu
            ms[middle] > target) right = middle - 1if (nums[middle] < target) left = middle + 1
[left, right):
            left = 0, right = nums.length;
            while (left < right),要使用 <if (nums[middle] > target) right = middle
            if (nums[middle] < target) left = middle + 1       

leetcode 704

题目链接:leetcode 704

代码:

	// 1
	// if(nums.indexOf(target) !== -1){
    //     return nums.indexOf(target)
    // }else{
    //     return -1
    // }
    
    // 2、 [left,right]
    //  let middle,left = 0,right = nums.length -1;
    //  while(left <= right){
    //      middle = Math.floor((left + right)/2)
    //      if(nums[middle] > target){
    //          right = middle -1
    //      }else if(nums[middle] < target){
    //          left = middle + 1
    //      }else{
    //          return middle
    //      }
    //  }
    //  return -1
    
    // 3、 [left, right)
    let middle,left = 0, right = nums.length
    while(left < right){
        middle = Math.floor((left + right) / 2)
        if(nums[middle] > target){
            right = middle
        }else if(nums[middle] < target){
            left = middle +1
        }else{
            return middle
        }
    }
    return -1

相关题目:

35.搜索插入位置(opens new window)
34.在排序数组中查找元素的第一个和最后一个位置(opens new window)
69.x 的平方根
367.有效的完全平方数

双指针

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

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

     数组中的元素为什么不能删除,主要是因为以下两点:
     	数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束,回收内存栈空间)。
     	C++中vector和array的区别一定要弄清楚,vector的底层实现是array,封装后使用更友好。
    
  • 很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

leetcode 27

解题思路:

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

         快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
         慢指针:指向更新新数组下标的位置
    

题目链接:leetcode 27

代码:

 	//  暴力实现: 第一层for:遍历数组;第二层for:向前覆盖删除的元素
    // for(let i =0;i<nums.length;i++){
    //     if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
    //         for (let j = i + 1; j < nums.length; j++) {
    //             nums[j - 1] = nums[j];
    //         }
    //         i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
    //         nums.length--; // 此时数组的大小-1
    //     }
    // }
    // return nums.length

    // 快慢指针
    let slow = 0;
    for(let fast = 0; fast < nums.length; fast++){
        if(nums[fast] !== val){
            nums[slow] = nums[fast]
            slow++
        }
    }
    return slow

leetcode 977

题目描述:非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

题目链接:leetcode 977

代码:

var sortedSquares = function(nums) {
    /**
    数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
    双指针:从左右向中间移动,i(左), j(右)
            定义一个存平方值的新数组result,大小和nums一致,下标值k从最大向最小方向存值。
            i++ : 左边的大,存到新数组result[k]中, k--
            j--: 右边的大,存到新数组result[k]中, k--
     */

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

题目推荐:

26.删除排序数组中的重复项
283.移动零
844.比较含退格的字符串
977.有序数组的平方

滑动窗口

O(n)

  • 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
  • 滑动窗口:不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
  • 滑动窗口可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口。
  • 滑动窗口的精华:如何移动起始位置

leetcode 209

题目描述:
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

题目链接:leetcode 209

代码:

var minSubArrayLen = function(target, nums) {
    /**
    在本题中实现滑动窗口,主要确定如下三点:
        窗口内是什么?
        如何移动窗口的起始位置?
        如何移动窗口的结束位置?
    窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
    窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
    窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
     */
    let start = 0,
     sum = 0, 
     len = 0, 
     result = Infinity;//子数组长度
    for(let end = 0; end < nums.length; end++){
        sum = sum + nums[end]
        while(sum >= target){ //如果用if,满足一次sums >= target,就跳出当前for循环;所以用while
           sum = sum - nums[start];
           len = end - start + 1; // len保存的当前的子数组长度 ==》 要找到长度最小的,每次都要比较留下最小的
           result = Math.min(result,len)
           start++;
        }
    }
    return result === Infinity ? 0 : result
};

相关题目:

904.水果成篮
76.最小覆盖子串

模拟行为

循环不变量原则(左闭右开/左开右闭)
感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的

leetcode 59 螺旋矩阵

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

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

思路:
本题并不涉及到什么算法,就是模拟过程。
循环不变量原则:循环中,处理边界。

本题依然是要坚持循环不变量原则:
模拟顺时针画矩阵的过程:
	填充上行从左到右
	填充右列从上到下
	填充下行从右到左
	填充左列从下到上
	由外向内一圈一圈这么画下去。
这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。
这里一圈下来,要画每四条边,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

题目链接:leetcode 59

代码:

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)); // n×n数组
    while(loop--){// [row,col]
        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 > startY; col--) {
            res[row][col] = count++;
        }
        // 左列做下到上(左闭右开)
        for (; row > startX; row--) {
            res[row][col] = count++;
        }

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

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

相关题目:

54.螺旋矩阵
剑指Offer 29.顺时针打印矩阵

链表

基础知识

  • 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域,一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
    链表的入口节点称为链表的头结点也就是head。
  • 链表的类型
    单链表 / 双链表 / 循环链表
  • 链表的存储方式
    数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
    链表是通过指针域的指针链接在内存中各个节点。
    所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
  • 链表的定义
  • 链表的操作
    删除节点
    添加节点
class ListNode {
  val;
  next = null;
  constructor(value) {
    this.val = value;
    this.next = null;
  }
}
  • 链表操作的两种方式:

1、直接使用原来的链表来进行删除操作:处理头节点和其他非头节点

​ return head

2、设置一个虚拟头结点在进行删除操作。

  • 定义临时指针: cur,指向题目中的头节点,来操作临时指针cur,来遍历链表,使得返回的head头指针不会被改变
  • cur = dynamicHead ; return dynamicHead.next
  • 修改某一个节点的指向(删除/添加节点等): 操作指针一定是该节点的前一个节点的指针

leetcode 203 移除链表元素

思路:
链表操作的两种方式:

直接使用原来的链表来进行删除操作:处理头节点和其他非头节点
设置一个虚拟头结点在进行删除操作。

删除的是头结点:
直接使用原来的链表来进行移除:将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。
设置一个虚拟头结点在进行删除操作:原链表的所有节点(包括头节点)就都可以按照统一的方式进行移除了

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

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

题目链接:leetcode 203

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function(head, val) {
    // 方法一、直接使用原来的链表来进行移除节点操作
    while(head !== null && head.val === val){//移除头节点
        head = head.next
    }
    let cur = head; //定义临时指针cur进行遍历链表,使head的值不变,返回原先链表的头节点
    while(cur !== null && cur.next !== null){//移除非头节点
        if(cur.next.val === val){
            cur.next = cur.next.next
        }else{
            cur = cur.next
        }
    }
    return head

    // // 设置一个虚拟头结点在进行移除节点操作 (虚拟节点.next = head),因此不需要单独去判断头节点
    // let dyanmiHead = new ListNode(0,head) // 定义虚拟头节点(虚拟头节点.val = 0; 虚拟头节点.next = head)
    // let cur = dyanmiHead // 定义临时指针来遍历链表,使得dyanmiHead的链表不被改变
    // while(cur !== null && cur.next !== null){
    //     if(cur.next.val === val){
    //         cur.next = cur.next.next
    //     }else{
    //         cur = cur.next
    //     }
    // }
    // return dyanmiHead.next
};

leetcode 707 设计链表

思路:

题目描述:

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性: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 个节点。

题目链接:leetcode 707

代码:


leetcode 206 反转链表

思路:双指针

题目描述:

​ 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

​ 示例 1:

​ 输入:head = [1,2,3,4,5]
​ 输出:[5,4,3,2,1]

题目链接:leetcode 206

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    /**
    双指针:
    fast(指向头节点的前一个指针),slow(指向头指针),temp(临时指针,保存slow.next,为了形成链)
     */
     let fast = null, slow = head, temp = null
     while(slow !== null){
         temp = slow.next;
         slow.next = fast;
        //  注意顺序
         fast = slow;
         slow = temp
     }
     return fast
};

leetcode 24. 两两交换链表中的节点

题目描述:

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

​ 示例 1:

​ 输入:head = [1,2,3,4]
​ 输出:[2,1,4,3]

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    /**
        修改某一个节点的指向: 操作指针一定是该节点的前一个节点的指针
        临时的节点:指向要操作的两个节点的前一个节点
     */
    // 定义虚拟头节点
    let dynamicHead = new ListNode(0,head)
    dynamicHead.next = head
    // 定义临时节点,指向虚拟头节点
    let cur = dynamicHead
    // 定义保存指针
    let temp = null
    let temp1 = null
    //边界 : cur.next !== null 为偶数;cur.next.next !== null 为奇数
    while(cur.next !== null && cur.next.next !== null){
        temp = cur.next;
        temp1 = cur.next.next.next
        // 交换节点 -- 使得cur.next 不指向 head了,所以之前要定义保存的指针来保存cur.next
        cur.next = cur.next.next;

        cur.next.next = temp;
        temp.next = temp1;
        cur = cur.next.next
    }
    return dynamicHead.next
};

leetcode19. 删除链表的倒数第 N 个结点

思路:

找到倒数第n个节点 --》 操作指针一定指向倒数第n个节点的前一个节点

双指针:如果要删除倒数第n个节点,让fast移动n+1步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow.next所指向的节点就可以了。

题目描述:

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

​ 示例 1:

​ 输入:head = [1,2,3,4,5], n = 2
​ 输出:[1,2,3,5]

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let dynamicHead = new ListNode(0,head)
    let fast = slow = dynamicHead;
    n = n+1
    // fast 向后走n+1
    while(n !== 0 && fast !== null){
        fast = fast.next
        n--
    }
    //  fast 和 slow同时走
    while(fast !== null){
        fast = fast.next
        slow = slow.next
    }
    // 删除
    slow.next = slow.next.next

    return dynamicHead.next
};

leetcode面试题 02.07. 链表相交

题目描述:

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

leetcode 142. 环形链表 II

思路:

判断是否有环:快慢指针,指针相遇即有环;

​ 从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个 链表有环。

fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

找环的入口

​ 假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入 口节点节点数为 z。

题目描述:

​ 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

​ 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递, 仅仅是为了标识链表的实际情况。

​ 不允许修改 链表。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值