LeetCode随笔
两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值target 的那两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
暴力法
记录下数组第一个数值,对数组进行循环,将之后的值与记录的数值相加,观察是否与目标值相等,相等则返回;不相等,记录数值第二个值,继续循环数组寻找…
优化:在固定数组第一个值后,循环时可以在第二个值开始循环;固定第二个值后,循环也不需要在第一个第二个上进行(固定第一个值时已经相加过),可以在第三个值上开始循环…
- 设定数组下标 i 遍历数组寻找第一个值;
- 在i循环下,设定数组下标j遍历数组寻找第二个值;
- 判断第一个值与第二个值之和是否与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
可以循环数组一遍,循环过程中查看当下值与目标值的差值是否存在于额外的空间中,如不存在,将每一次循环时的值保存再空间中,继续循环…
- 利用数组下标i遍历数组寻找第一个值;
- 在i循环下,使用 hashmap 的 containKey(key) 方法判断target - nums[i]是否在map中;
- 若差值不在 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] 这和算法运行的机制相关
若给出的数组是升序的
双指针
在数组是升序的情况下,可以看成寻找数值的问题, 先取左侧与中间的和与目标值比较,若目标值大,表名两个数应该在右侧较大部分,在右侧取中间值进行计算;反之,表名两个数在左侧较小部分,将在左侧取中间值进行计算…
- 设定左侧数组下标 l 和右侧数组下标r, 循环遍历每一个数,确定是否有数相加之和能构成目标值;
- 利用二分法查找数值 mid = l + (r - l) / 2
- 对nums[l]nums[mid]进行求和,判断和与target 是否相等;
- 若两数之和小于target ,将左侧数组下标 l 向右移动;
- 若两数之和大于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)…
- 设定两数相加的函数;
- 函数有一个形参n,令相加的数为n-1与n-2;
- 判断输入的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)
双指针
将前两个数的和保存起来,从第三个数开始循环到指定数值,循环中只有两个变量,将每一次的和与最大的数更新给变量,以此循环…
- 定义n=0时指针为i,n=1时指针为j;
- sum为指针为i与指针为j时数值的和;
- 循环遍历n次,每一次让j指向sum,i指向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 )
最大的乘积可能是 “最大的三个正数” 或者 “最小的两个负数与最大的正数”相乘
排序
先把数组排好序,寻找乘积最大的数。
- 先对数组进行排序,找到数组长度n;
- 利用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)
线性
上面分析时知道,最大乘积是在 “最大的三个正数” 或者 “最小的两个负数与最大的正数 ”这五个数之间产生,可以设定五个变量,将最大的正数先设置负无穷,最小负数设置正无穷,这样方面在循环时,把所有的数组中的数都进行比较。
- 先定义五个相关变量min1<min2<max1<max2<max3 令最小的都为+无穷,最大的都为-无穷,这样可以令数组的数都遍历到;
- 循环遍历一遍数组,当数x < min1时,对min1赋值为x,min2 赋值为 min1
- 当出现数x > max3时,对max3赋值为x,max2赋值为max3,max1赋值为max2;
- 出现数max3 >x > max2时,对max2赋值为x,max1赋值为max2
- 出现数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 进行指向…
- 先定义指针pre,curr,next_val;
- 需要保存当前的next指向的值 next_val = curr.next;
- 将链表的 next 指针指向前面,将curr.next指向pre ,pre初始值为空;
- 进行变量更新 pre = curr, curr = next_val;
- 输出最后的头结点;
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 指向空,代表此结点是最后连接的值;
- 后一结点指向前一结点 head.next.next = head ;
- 前一结点的 next 指向空 head.next = null
- 递归
//静态内部类
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 。
- 考虑寻找平方根的过程,就是在找正整数的平方与目标值更接近
二分查找
寻找平方根,利用二分法找出中间的值,比较值平方与目标的差值。
- 先定义指针index 代表查找的位置;
- 将变量l 设为0 变量r 设为x ,其中x 为目标数,取中间位置 l + (r - l) / 2 可预防数字过大造成的损失
- 判断mid 平方与目标值 x 的大小
- mid 大 将r 设为 mid - 1
- 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 进行迭代
牛顿迭代
- 先定变量义resu 保存,x 与 n/x 的中间值 ;
- 判断结果变量resu 与值n 是否相等
- 不相等时,进入迭代
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 用此决定循环的结束
快慢指针
设置不同速度的变量代表不同节点的值,如果有环形出现,不同速度的节点值一定会有重合的情况,若没有环形出现,快的节点的指向为空。
- 先定义快慢指针quick ,slow 代表查找的位置;
- 将快指针设置为head.next 慢指针设置为head 起始点
- 判断两个数是否相同
- 不同时 将slow = slow.next, quick = quick.next.next
- 多一个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)