01-线性表-算法

线性表相关算法

Java Array

11. 盛最多水的容器

思路:

1、蛮力法:

将所有的组合全部计算,找出其中最大值;

分析:时间复杂度:O(n2) [因为需要 n * (n +1) / 2 次组合,所以复杂度],空间复杂度:O(1),常数级,因为没有辅助数组;

2、双指针法:

在数组双端设置两个标记量,视作两个指针;保留指向的两个值中更大的那个数值,另一个指针向中间移动,再判断组成面积的大小,更大则记录,否则不记录;继续循环,直到两个指针相遇。

分析:时间复杂度:O(n) [因为只需要便利一次数组即可],空间复杂度:O[1],常数级,理由同上。
注:之所以需要保留更大的数值,因为围成的面积是由 两指针数据较小值两指针的距离有关,移动指针会减小 两指针间距离,只需要保留较大的指针数值,就能保证之后围成的面积会尽可能保留更大值;

相关代码 [双指针法]:点击查看题解

class Solution {
    public int maxArea(int[] height) {
        int front = 0, after = height.length - 1;	// 这里定义双指针
        int max_area = 0, area = 0, high = 0;		// 这里定义的是 最大面积、过程中的面积 和 过程中的高度

        while (front < after) {	// 循环条件,两指针不相遇
            int wide = after - front;
            high = height[front] > height[after] ? height[after--] : height[front++];	// 用三元表达式判断大小,不用 Math 工具类,可以在判断大小的时候顺便将指针移动了,自增、自减符号放在后面即是先执行此步骤再执行自增自减;
            area = wide * high;
            
            if (area > max_area)	// 判断过程量是否比之前的最大值更大,是则改变最大值
                max_area = area;
        }

        return max_area;
    }
}

另一个思路:

class Solution {
    public int maxArea(int[] height) {
        int res = 0;	// 定义将返回的结果,即最大面积
        int right = height.length-1, left = 0, hight;	// 定义双指针 和 过程中高度

        while (left<right){	// 循环条件仍是两指针不相遇
            if(height[left]>height[right]){	// 判断两指针指向的数值大小
                hight = height[right];	// 记录较小值的大小,
                res = Math.max(hight*(right-left),res);	// 判断、记录最大值
            	
                while(left<right&&hight>=height[right])	// 在做右指针未相遇 且没有之前的 记录值hight 更大,则移动指针
                	right--;
            } else {
                hight = height[left];
                res = Math.max(hight*(right-left),res);
                
                while (left<right&&hight>=height[left])	// 原理同上
                	left++;
            } 
        }
        
        return res;
    }
}

283. 移动零

思路:

1、蛮力法:

思路反而复杂,不推荐;因为遇到 一个零多零并排 的时候情况不同,可能需要多次循环消除 多零并排 的影响;

时空复杂度,因为没有具体算法思路,暂不讨论;

2、计数零法 [count zeros]

用一个变量记录 0 出现的次数,将对应数向前移动对应的位,并将原位置标零;

时间复杂度:O(n),空间复杂度:O(1)

对应代码:

class Solution {
    public void moveZeroes(int[] nums) {
        if (nums == null || nums.length == 0) 
            return ;

        int count = 0;	// 用于记录 0 出现的次数

        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) 	// 如果出现了 0 值,count 进行记录
                count++;
            else {	// 将所有的 非零数 前移 count 位,并将遍历到的位置置零
                if (count == 0)	// 如果 count 为零就不需要进行操作了,否则可能出现错误
                    continue;
                    
                nums[i - count] = nums[i];
                nums[i] = 0;
            }
        }
    }
}

3、双指针法:点击查看题解

设置两个指针,一个用于记录非零数,一个正常遍历,当两个指针指向不同时,将非零数直接赋值给正常遍历数;

时间复杂度:O(n),空间复杂度:O(1);

代码示例:

class Solution {
    public void moveZeroes(int[] nums)  {
        if (nums == null || nums.length == 0) {
            return;
        }
        int zero = 0;	// 这里为正常遍历的指针
        for (int _zero = 0; _zero < nums.length; _zero++) {	// 这里可以看作 寻找非零数的指针
            if (nums[_zero] != 0) {
                if (_zero > zero) {	// 如果非零数零数的后面,则说明有零,更换两指针指向的位置的值
                    nums[zero] = nums[_zero];
                    nums[_zero] = 0;
                }
                zero++;
            }
        }
    }
}

70. 爬楼梯

思路

先不管具体使用什么思路,首先需要将本题进行抽象,到底在问什么问题;根据已有的数据,可以了解这道题其实就是一个具象化的斐波拉契数列;我们假设第 k 级台阶 有 ak 种走法,则:

a1 = 1; a2 = 2;

ak = ak-1 + ak-2 [k >= 3];

因为,第k级台阶的走法只能从 第 k - 1 级台阶第 k - 2 级台阶 走过去,所以我们可以根据这个完成相关的算法设计:

相关代码:迭代法

class Solution {
    public int climbStairs(int n) {
        if (n <= 2) return n;	// 如果只问前两层的直接返回对应值;因为我们知道前两级的走法正好等于它的级数
        
        int f = 1, r = 2, tmp = 0;	// 设置三个变量,准备迭代,tmp 用于承接空值,f 为相邻两级台阶的前一级的走法,r 为后一级
        int l = 3;	// l 即 level,表示的是正在求的层数

        while (l++ <= n) {	// 使用 l++ 而不是 ++l 是因为我们的级数设计是从 3 开始,而第三级台阶的走法在之前的数据中并没有计算出来
            tmp = r;	// 使用辗转相加,r 一直表示后一级台阶的走法数量,f 表示前一级
            r = f + tmp;
            f = tmp;
        }

        return r;	// 最后跳出循环的 r 即为所求
    }
}

算法分析:时间复杂度:O(n),一次循环;空间复杂度:O(1),使用的是常量级的变量;

方法二:记忆化存储[斐波拉契数列的常规解法]

class Solution {
    private int[] stairsCount = null;	// 设置一个全局变量,记录各级台阶的走法
    public int climbStairs(int n) {
        if (n <= 2) return n;	// 简化运算

        stairsCount = new int[n];	// 生成对应位数的记录数据

        countMethod(n - 1);		// 调用递归填充数组

        return stairsCount[n - 1];	// 返回 第 n 级台阶的数,因为索引是从零开始的
    }

    public int countMethod(int n) {
        if (n < 2)	// n < 2 的时候用 返回其对应的数据,并存入记忆化数组中
            return stairsCount[n] = n + 1;

        if (stairsCount[n] == 0)	// 递归调用,返回对应值
            stairsCount[n] = countMethod(n - 1) + countMethod(n - 2);

        return stairsCount[n];	// 返回当前层数据
    }
}

在递归使用的过程中,希望大家能想象一下它的数据下探模式,在 17行 其实就已经在调用递归函数,在此函数未结束的时候调用新函数,在下探到第 0 \ 1 的索引时获取确定的数据,再将之返回,完成数据填充;在调用同行第二个函数的时候,所有值都已经填充完毕,所以只需消耗获取数组索引的时间;

算法分析:时间复杂度:O(n),其实只下探了一次,其余数据可以直接根据数组获得;空间复杂度:O(n),使用了线性的辅助内存空间。

15. 三数之和

思路

1、蛮力法:直接三层嵌套递归,逐一比对;

分析:时间复杂度:O(n3),因为需要三层递归;空间复杂度:O(1),只是用了常量级的额外空间;

时间复杂度太高,不推荐,愿意的话可以自行完成相关代码;

2、双指针法:

首先将数组进行排序,调用方法类,时间复杂度为 O(n log2n);因为要找和为 0 的三个数,且数组有序;所以选择定一变二,先定住一个数,再在其他数中找和为此数相反数的两个数;这个定住的数按照脚标 [或者说索引] 依次更迭,且定住的数和前一次不能相同;找相反数的时候使用双指针法,在 O(n) 的时间复杂度下完成数据查找;

相关代码:力扣国际版讨论

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();	// 返回的数据,根据题目设置
        int len = nums.length;
        if (nums == null || len < 3) return ans;		// 前置判定
        
        Arrays.sort(nums);	// 数据排序
        
        for (int i = 0; i < len - 2; i++) {			// 三个数,所以不需要 len
            if (nums[i] > 0) return ans;			// 负数时候已经计算了正数了,没必要二次记录
            if (i > 0 && nums[i] == nums[i - 1]) continue;	// 定的数和上一次定的数不相重合
            
            int lo = i + 1, hi = len - 1, sum = 0 - nums[i];	// 设置双指针,和目标值
            
            while (lo < hi) {
                if (nums[lo] + nums[hi] == sum) {
                    ans.add(Arrays.asList(nums[i], nums[lo], nums[hi]));	// 满足需求,添加到返回值中
                    while (lo < hi && nums[lo+1] == nums[lo]) lo++;			// 为了保证不会出现重复数据
                    while (lo < hi && nums[hi-1] == nums[hi]) hi--;			// 同上
                    lo++;
                    hi--;
                } else if (nums[lo] + nums[hi] < sum) {		// 如果和值小于目标值,需要将较小值指针后移
                    lo++;
                } else {		// 和值大于目标值,较大值指针前移
                    hi--;
                }
            }
        }
        
        return ans;
    }
}

Java List

206. 反转链表

思路

使用迭代,依次倒置节点

本题题解和拓展

本题图解

能力有限,做不出图解,有兴趣的可以去看看相关的,都是力扣的精选题解;

相关代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null, curr = head;	// 前置节点和当前节点
        
        while (curr != null) {	// 只要当前节点不为空则需要继续倒置
            ListNode nextTemp = curr.next;	// 记录当前节点的下一节点
            curr.next = prev;				// 将当前节点的后置节点改为前置节点
            prev = curr;					// 将当前节点付给前置节点变量,准备下一循环
            curr = nextTemp;				// 当前节点赋值为原节点的后置节点 nextTemp
    	}
        
    	return prev;	// prev 即为倒置链表的头节点
	}
}

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

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null)
            return head;
        
        ListNode front = head, behind = head.next;
        front.next = swapPairs(behind.next);
        behind.next = front;

        return behind;
    }
}
/*
class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) return head;
        
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        
        ListNode front = null;
        ListNode after = null;
        ListNode pre = dummy;
        
        while (head != null && head.next != null) {
            front = head;
            after = head.next;
            
            pre.next = after;
            front.next = after.next;
            after.next = front;
            
            pre = front;
            head = pre.next;
        }
        
        return dummy.next;
    }
}
*/

后续持续更新,一直刷算法写题解挺难顶的,见谅哈,逐步更新

posted @ 2020-12-30 11:16  JE_Chris  阅读(149)  评论(0)    收藏  举报