LeetCode刷题笔记

LeetCode随笔

两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值target 的那两个整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

暴力法

记录下数组第一个数值,对数组进行循环,将之后的值与记录的数值相加,观察是否与目标值相等,相等则返回;不相等,记录数值第二个值,继续循环数组寻找…

优化:在固定数组第一个值后,循环时可以在第二个值开始循环;固定第二个值后,循环也不需要在第一个第二个上进行(固定第一个值时已经相加过),可以在第三个值上开始循环…

  1. 设定数组下标 i 遍历数组寻找第一个值;
  2. i循环下,设定数组下标j遍历数组寻找第二个值;
  3. 判断第一个值与第二个值之和是否与target 相等;
public static void main(String[] args){
   int[] nums = new int[]{3, 4, 2, 1, 5, 6};
   int target = 10;
   // 使用toString 打印数组,否则输出的为数组的地址
   System.out.println(Arrays.toString(Sum(nums, target)));
}

// 返回值是一个数组类型,函数形参是数组与目标值
public static int[] Sum(int[] nums, int target) {
    // 记录数组长度
   int n = nums.length;
    // 循环数组下标 0,1,2,3,4,5
   for (int i = 0; i < n; i++) {
       // 循环变量 i 之后的下标,节省运算时间  1--5, 2--5, 3--5, 4--5, 5
       for (int j = i + 1; j < n; j++) {
           if (nums[i] + nums[j] == target) {
               return new int[]{i, j};
           }
       }
   }
   // 循环遍历后,没有找到数值返回0
   return new int[0];
}
时间复杂度:O(N^2)
空间复杂度:O(1)

hashmap

可以循环数组一遍,循环过程中查看当下值与目标值的差值是否存在于额外的空间中,如不存在,将每一次循环时的值保存再空间中,继续循环…

  1. 利用数组下标i遍历数组寻找第一个值;
  2. i循环下,使用 hashmapcontainKey(key) 方法判断target - nums[i]是否在map中;
  3. 若差值不在 map 中,将此刻的nums[i]值存入 hashmap 中;
public static void main(String[] args){
   int[] nums = new int[]{3, 4, 2, 1, 5, 6};
   int target = 10;
   System.out.println(Arrays.toString(Sum2(nums, target)));
}

public static int[] Sum2(int[] nums, int target) {
    // 记录数组长度
   int n = nums.length;
   Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
    // 循环数组下标 0,1,2,3,4,5
   for (int i = 0; i < nums.length; ++i) {
       // 判断该key在map中是否有key存在
       if (hashtable.containsKey(target - nums[i])) {
           return new int[]{hashtable.get(target - nums[i]), i};
       }
       //map 中不存在key 将此次循环的 值与下标保存到map中
       hashtable.put(nums[i], i);
   }
   return new int[0];
}
时间复杂度:O(N)
空间复杂度:O(N)

——————
map是一个key和value的键值对集合。

map中的containKey(key)方法是判断该key在map中是否有key存在。如果存在则返回true,反之,返回false。

在哈希表中进行添加,删除,查找等操作,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),时间消耗是很少的。

上述的算法,当假设每种输入只会对应一个答案不成立时,nums[3, 4, 2, 1, 6, 7]
输出的值不同 暴力法[0, 5] hashmap[1, 4] 这和算法运行的机制相关

若给出的数组是升序的

双指针

在数组是升序的情况下,可以看成寻找数值的问题, 先取左侧与中间的和与目标值比较,若目标值大,表名两个数应该在右侧较大部分,在右侧取中间值进行计算;反之,表名两个数在左侧较小部分,将在左侧取中间值进行计算…

  1. 设定左侧数组下标 l 和右侧数组下标r, 循环遍历每一个数,确定是否有数相加之和能构成目标值;
  2. 利用二分法查找数值 mid = l + (r - l) / 2
  3. nums[l]nums[mid]进行求和,判断和与target 是否相等;
  4. 若两数之和小于target ,将左侧数组下标 l 向右移动;
  5. 若两数之和大于target ,将右侧数组下标 r 向左移动;
public static void main(String[] args){
   int[] nums = new int[]{1, 2, 3, 4, 5, 6};
   int target = 10;
   System.out.println(Arrays.toString(Sum(nums, target)));
}

public static int[] Sum3(int[] nums, int target) {
    // 记录数组长度
    int n = nums.length;
    // 循环数组下标 0,1,2,3,4,5
    for (int i = 0; i < n; i++) {
       // 将左侧的下标设为i
       int l = i;
       // 将右侧的下标设为n-1
       int r = n - 1;
       // 左侧下标 小于等于 右侧下标就继续循环
       while(l <= r){
           int mid = l + (r - l) / 2
           if (nums[mid] == target - nums[i]) {
               return new int[]{i, mid};
           }else if(nums[mid] > target - nums[i]){
               r = mid -1;
           }else{
               l = mid +1;
           }
       }
   }
   // 循环遍历后,没有找到数值返回0
   return new int[0];
}
时间复杂度:O(Nlog(N))
空间复杂度:O(1)

还有一个 移动版

斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

递归

从第三个数开始都是前面两个数的和,可以利用公式 ,例如求 f(5)
f(5)
= f(4) + f(3)
= f(3) + f(2) + f(2) + f(1)
= f(2) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)…

  1. 设定两数相加的函数;
  2. 函数有一个形参n,令相加的数为n-1n-2
  3. 判断输入的n大于1,令n=0时 返回0,n=1时返回1;
   public static void main(String[] args) {
       System.out.println(cal(7));
   }

   public static int cal(int num){
       // 第一个数0
       if(num == 0){
           return 0;
       }
       // 第二个数1
       if(num == 1){
           return 1;
       }
       // 递归
       int temp = cal(num - 1) + cal(num - 2);
       return temp;
   }
时间复杂度:O(2^N)
空间复杂度:O(1)

双指针

将前两个数的和保存起来,从第三个数开始循环到指定数值,循环中只有两个变量,将每一次的和与最大的数更新给变量,以此循环…

  1. 定义n=0时指针为in=1时指针为j
  2. sum为指针为i与指针为j时数值的和;
  3. 循环遍历n次,每一次让j指向sumi指向j
public static void main(String[] args) {
        System.out.println(cal2(10));
    }

    public static int cal2(int num){
        if(num == 0){
            return 0;
        }
        if(num == 1){
            return 1;
        }
        int i = 0;
        int j = 1;
        for(int n = 2; n <= num; n++){
            int sum = i + j;
            i = j;
            j = sum;
        }
        return j;
    }
时间复杂度:O(N)
空间复杂度:O(1)

三个数最大乘积

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

  • 考虑整数正负的情况
    1.全为正数时,找出最大的三个相乘
    2.全为负数时,同样找出最大的三个相乘
    3.有一个负数,找最大的三个或者不成立
    4.有一个正数,找最小的两个数与最大的数
    5.有0时都符合以上(负数时 相乘最大为0,正数时 0取不到或者或者相乘为0 )

最大的乘积可能是 “最大的三个正数” 或者 “最小的两个负数与最大的正数”相乘

排序

先把数组排好序,寻找乘积最大的数。

  1. 先对数组进行排序,找到数组长度n;
  2. 利用Math.max函数找出nums[n-1]nums[n-2]nums[n-3]nums[1]nums[0] 进行相乘比较;
public static void main(String[] args){
   int[] nums = new int[]{3, 4, 2, 1, 5, 6};
   System.out.println(Aim(nums));
}

// Arrays.sort(nums)    时间复杂度是nlogn
public static int Aim(int[] nums) {
   Arrays.sort(nums); 
   int n = nums.length;
   int numb = Math.max(nums[0] * nums[1] * nums[n-1] , nums[n-1] * nums[n-2] * nums[n-3]);
   return numb;
}
时间复杂度:O(Nlog(N))
空间复杂度:O(1)

线性

上面分析时知道,最大乘积是在 “最大的三个正数” 或者 “最小的两个负数与最大的正数 ”这五个数之间产生,可以设定五个变量,将最大的正数先设置负无穷,最小负数设置正无穷,这样方面在循环时,把所有的数组中的数都进行比较。

  1. 先定义五个相关变量min1<min2<max1<max2<max3 令最小的都为+无穷,最大的都为-无穷,这样可以令数组的数都遍历到;
  2. 循环遍历一遍数组,当数x < min1时,对min1赋值为xmin2 赋值为 min1
  3. 当出现数x > max3时,对max3赋值为xmax2赋值为max3max1赋值为max2
  4. 出现数max3 >x > max2时,对max2赋值为xmax1赋值为max2
  5. 出现数max2 >x > max1时,对max1赋值为x
public static void main(String[] args){
   int[] nums = new int[]{3, 4, 2, 1, 5, 6};
   System.out.println(Aim2(nums));
}

public static int Aim2(int[] nums) {
   int min1 = Integer.MAX_VALUE;
   int min2 = Integer.MAX_VALUE;
   int max1 = Integer.MIN_VALUE;
   int max2 = Integer.MIN_VALUE;
   int max3 = Integer.MIN_VALUE;
   for(int x : nums){
       if(x < min1){
           min2 = min1;
           min1 = x;
       }else if(x < min2){
           min2 = x;
       }
       if(x > max3){
           max1 = max2;
           max2 = max3;
           max3 = x;
       }else if(x > max2){
           max1 = max2;
           max2 = x;
       }else{max1 = x;}
   }
   int numb = Math.max(min1 * min2 * max3 , max1 * max2 * max3);
   return numb;
}
时间复杂度:O(N)
空间复杂度:O(1)

反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

迭代

目的是将链表翻转过来,可以将后一结点的 next 指向前一个结点,从而达到翻转的效果;
但是链表是根据 next 来寻找下一结点的,将后一结点的 next 指向前一个结点后,后一结点之后的结点就会找不到,可以将后一结点的 next 先保存,此外还需将前一结点的值也进行保存,方便后一结点的 next 进行指向…

  1. 先定义指针precurrnext_val
  2. 需要保存当前的next指向的值 next_val = curr.next
  3. 将链表的 next 指针指向前面,将curr.next指向prepre初始值为空;
  4. 进行变量更新 pre = currcurr = next_val
  5. 输出最后的头结点;
static class ListNode{
    int val;
    ListNode next;

    //内部类的构造方法 传入变量值
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

public static void main(String[] args) {
    ListNode node5 = new ListNode(5, null);
    ListNode node4 = new ListNode(4, node5);
    ListNode node3 = new ListNode(3, node4);
    ListNode node2 = new ListNode(2, node3);
    ListNode node1 = new ListNode(1, node2);
    ListNode node = itre(node1);
    System.out.println(node);
}

public static ListNode itre(ListNode head){
    // 先将前指针赋初值空
    ListNode pre = null;
    ListNode next_val;
    // 开始时当前值为头结点的值
    ListNode curr = head;
    while (curr != null){
        // 将当前结点的next 进行保存
        next_val = curr.next;
        // 将当前结点的next 指向前一结点
        curr.next = pre;
        // 变量的更新
        pre = curr;
        // 变量的更新
        curr = next_val;
    }
    return pre;
}

递归

从链表末尾开始,将链表进行反转,先从后两个结点开始,将后一结点的 next 指向前一结点,前一结点的 next 指向空,递归进行下一结点…
当此刻的结点为空,或者此刻结点的 next 指向空,代表此结点是最后连接的值;

  1. 后一结点指向前一结点 head.next.next = head
  2. 前一结点的 next 指向空 head.next = null
  3. 递归
 //静态内部类
static class ListNode{
    int val;
    ListNode next;

    //内部类的构造方法 传入变量值
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

public static void main(String[] args) {
    ListNode node5 = new ListNode(5, null);
    ListNode node4 = new ListNode(4, node5);
    ListNode node3 = new ListNode(3, node4);
    ListNode node2 = new ListNode(2, node3);
    ListNode node1 = new ListNode(1, node2);
    ListNode node = itre2(node1);
    System.out.println(node);
}

public static ListNode itre2(ListNode head){
    if(head == null || head.next == null){
        return head;
    }
    ListNode newHead = itre2(head.next);
    // 将下一结点的next 指向自己
    head.next.next = head;
    // 自己的next 指向空
    head.next = null;
    return newHead;
}

x的平方根

给你一个非负整数x ,计算并返回 x 的算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

  • 考虑寻找平方根的过程,就是在找正整数的平方与目标值更接近

二分查找

寻找平方根,利用二分法找出中间的值,比较值平方与目标的差值。

  1. 先定义指针index 代表查找的位置;
  2. 将变量l 设为0 变量r 设为x ,其中x 为目标数,取中间位置 l + (r - l) / 2 可预防数字过大造成的损失
  3. 判断mid 平方与目标值 x 的大小
  4. mid 大 将r 设为 mid - 1
  5. mid 小 将l 设为 mid + 1
public class Sqrt {
   public static void main(String[] args){
       int nums = 10;
       System.out.println(Sqrt(nums));
   }

   public static int Sqrt(int n) {
       int index = -1;
       int l = 0;  // 左指针
       int r = n;  //右指针
       // 判断循环终止条件
       while(l <= r){
           int mid =  l + (r - l) / 2;
           if(mid * mid <= n){
               // 只保留整数部分,让较小的部分赋值
               index = mid;
               l = mid + 1;
           }else{
               r = mid - 1;
           }
       }
       return index;
   }
}
时间复杂度:O(log(N))
空间复杂度:O(1)
  • 寻找平方根的过程,例如16
    1 x 16
    2 x 8
    4 x 4
    8 x 2
    16 x 1
    可以看成寻找 x^2 = n ,x = n / x , 用 (x + n / x)/ 2 进行迭代

牛顿迭代

  1. 先定变量义resu 保存,xn/x 的中间值 ;
  2. 判断结果变量resu 与值n 是否相等
  3. 不相等时,进入迭代
public static void main(String[] args){
       System.out.println(newton(36));
   }

public static int newton(int n ){
   if(n == 0){
       return 0;
   }
   int a = (int)Aim2(n,n);
   return a;
}

public static double Aim2(double x, int n) {
   double resu = (x + n / x) / 2;
   if(resu == x){
       return x;
   }else{
       return Aim2(resu, n);
   }
}

环形列表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

  • 考虑环代表循环到一定程度后,会有重复的值出现
    在循环过程中,若没有环形列表则一定会有某一数的next 为null 用此决定循环的结束

快慢指针

设置不同速度的变量代表不同节点的值,如果有环形出现,不同速度的节点值一定会有重合的情况,若没有环形出现,快的节点的指向为空。

  1. 先定义快慢指针quickslow 代表查找的位置;
  2. 将快指针设置为head.next 慢指针设置为head 起始点
  3. 判断两个数是否相同
  4. 不同时 将slow = slow.next, quick = quick.next.next
  5. 多一个next 体现不同的速度,也保证了进入环形后不会出现同步循环的情况
static class ListNode{
   int val;
   ListNode next;

   public ListNode(int val, ListNode next){
       this.val = val;
       this.next = next;
   }
}

public static void main(String[] args) {
   ListNode node5 = new ListNode(5, null);
   ListNode node4 = new ListNode(4, node5);
   ListNode node3 = new ListNode(3, node4);
   ListNode node2 = new ListNode(2, node3);
   ListNode node1 = new ListNode(1, node2);
   node5.next = node2;
   System.out.println(hashCycle2(node1));
}

public static boolean hashCycle(ListNode head){
   if(head == null){
       return false;
   }
   ListNode slow = head;
   ListNode quick = head.next;
   while(slow != quick){
       if(quick == null || quick.next == null){
           return false;
       }
       slow = slow.next;
       quick = quick.next.next;
   }
   return true;
}
时间复杂度:O(N)
空间复杂度:O(1)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值