LeetCode随笔
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值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
可以循环数组一遍,循环过程中查看当下值与目标值的差值是否存在于额外的空间中,如不存在,将每一次循环时的值保存再空间中,继续循环…
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] 这和算法运行的机制相关
若给出的数组是升序的
双指针
在数组是升序的情况下,可以看成寻找数值的问题, 先取左侧与中间的和与目标值比较,若目标值大,表名两个数应该在右侧较大部分,在右侧取中间值进行计算;反之,表名两个数在左侧较小部分,将在左侧取中间值进行计算…
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)…
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)
双指针
将前两个数的和保存起来,从第三个数开始循环到指定数值,循环中只有两个变量,将每一次的和与最大的数更新给变量,以此循环…
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 ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。
最大的乘积可能是 “最大的三个正数” 或者 “最小的两个负数与最大的正数”相乘
排序
先把数组排好序,寻找乘积最大的数。
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)
线性
上面分析时知道,最大乘积是在 “最大的三个正数” 或者 “最小的两个负数与最大的正数 ”这五个数之间产生,可以设定五个变量,将最大的正数先设置负无穷,最小负数设置正无穷,这样方面在循环时,把所有的数组中的数都进行比较。
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 进行指向…
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 指向空,代表此结点是最后连接的值;
//静态内部类
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 的算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
二分查找
寻找平方根,利用二分法找出中间的值,比较值平方与目标的差值。
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)
牛顿迭代
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 。
快慢指针
设置不同速度的变量代表不同节点的值,如果有环形出现,不同速度的节点值一定会有重合的情况,若没有环形出现,快的节点的指向为空。
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)