徒手挖地球九周目

徒手挖地球九周目

NO.22 括号生成 中等

lZC6kF.png

思路一:暴力法 1. 将2*n个括号的序列全部得到。2. 同时判断其是否为有效序列。如果是则加入结果集。

	List<String> res=new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        dfs(0,"",n);
        return res;
    }

//    深度优先遍历得到所有组合序列,如果是有效序列,则加入结果集
    public void dfs(int index,String conbination,int n){
        if (index==2*n){
            if (isValid(conbination))res.add(conbination);
            return;
        }else {
            dfs(index+1,conbination+'(',n);
            dfs(index+1,conbination+')',n);
        }
    }

//    平衡法判断括号序列是否有效
    public boolean isValid(String s){
        int balance=0;
        for (int i=0;i<s.length();i++){
            if (s.charAt(i)=='('){
                balance++;
            }else {
                balance--;
            }
//            如果balance<0则说明,)出现在与其对应的(之前,或者)多于(
            if (balance<0)return false;
        }
        return balance==0;
    }

时间复杂度:O(2^2n*n)

思路二:回溯法 该方法是对上面的暴力法的一个优化思路。上面的方法需要组合出所有的序列(有效的和无效的),思路就是不生成无效的序列(或者说是"剪枝",剪除无效无效序列)。观察有效序列的特点:1. 因为是括号’对’,所以n对括号序列中的’(‘和’)'的数量都是n个。2. ')‘不能出现在与其成对的’('之前。

针对上述细节,思考回溯算法细节:

  1. 当’(‘和’)'的数量都是n个的时候,说明已经得到括号序列。
  2. ‘(‘数量小于n的时候,可以向序列中继续添加’(’。
  3. ‘)‘数量小于n并且当前’)‘数量小于当前’(‘数量时,才可以向序列中继续添加’)’。
	List<String> res=new ArrayList<>();

    public List<String> generateParenthesis(int n) {
        dfs(0,0,"",n);
        return res;
    }

    /**
     * @param l 左括号数量
     * @param r 有括号数量
     * @param combination 当前括号序列
     * @param n 输入n
     */
    public void dfs(int l,int r,String combination,int n){
//        当'('和')'的数量都是n个的时候,说明已经得到括号序列。
        if (l==n&&r==n){
            res.add(combination);
            return;
        }
//        '('数量小于n的时候,可以向序列中继续添加'('。
        if (l<n){
            dfs(l+1,r,combination+'(',n);
        }
//        ')'数量小于n并且当前')'数量小于当前'('数量时,才可以向序列中继续添加')'。
        if (r<n&&r<l){
            dfs(l,r+1,combination+')',n);
        }
    }

时间复杂度:O(4^n/sqrt(n))。在回溯过程中,每个有效序列最多需要n步。

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

lZCWlR.png

思路一:迭代实现 用一个pre指针指向未被交换节点的前驱,交换pre后继和pre后继的后继,直到pre没有后继或者pre的后继没有后继。

public ListNode swapPairs(ListNode head) {
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        ListNode pre=dummy;
        while (pre.next!=null&&pre.next.next!=null){
            ListNode start=pre.next,end=pre.next.next;
//            交换两个节点,注意交换顺序,否则容易死循环
            pre.next=end;
            start.next=end.next;
            end.next=start;
//            移动pre指针,此时已经交换过两个节点的位置
            pre=start;
        }
        return dummy.next;
    }

时间复杂度:O(n)

思路二:递归实现 没有节点或者只有一个节点不需要进行交换,停止递归,此时返回head节点本身。每层递归返回值是交换过之后的链表。

public ListNode swapPairs(ListNode head) {
    if (head==null||head.next==null){
        return head;
    }
    ListNode next=head.next;
    head.next=swapPairs(next.next);
    next.next=head;
    return next;
}

NO.26 删除排序数组中的重复项 简单

lZCsTU.png

lZCrwT.png

思路一:双指针法 题目中给了两个关键点需要特别思考"原地"和"不需要考虑数组中超出新长度后面的元素",所谓的"原地"就是不需要创建新的数组将不重复的元素复制过去,只需要在原数组中进行"覆盖"即可;所谓"不需要考虑数组中超出新长度后面的元素"就是只需要将不重复元素都"紧凑到原数组的前面",如:[1,1,1,2,3,3,4,6]遵循上述两个点进行"覆盖"和"紧凑"的结果[1,2,3,4,6,3,4,6],算法的返回值为新长度5。

可以用两个指针i和j分别指向0号和1号元素,如果j指向的元素和i指向的元素相等就移动j指针,如果不相等则先移动i指针再让j指向的元素覆盖此时i指向的元素最后移动j指针,直至j指针遍历完所有元素。

public int removeDuplicates(int[] nums) {
        int len=nums.length;
        if (nums==null||len==0)return 0;
        int i=0;
        for (int j=1;j<len;j++){
//            如果不相等,则先移动i指针再让j指向的元素覆盖此时i指向的元素
            if (nums[i]!=nums[j]){
                i++;
                nums[i]=nums[j];
            }
        }
        return i+1;
    }

时间复杂度:O(n)

NO.27 移除元素 简单

lK1La6.png

lK1qVx.png

思路一:双指针法 这道题和第26题如出一辙,题目中的关键点也一样。算法的区别在于对数组第一个元素的处理,26题中第一个元素是不需要"覆盖"的,但是本题的第一个元素有可能需要进行"覆盖"。

用两个指针i和j同时指向0号元素,如果j号指针不等于val,就先将j号元素"覆盖"i号元素再移动i指针,如果j号元素等于val则移动j指针,循环直至j指针遍历完所有元素。

public int removeElement(int[] nums, int val) {
        if (nums==null||nums.length==0)return 0;
        int i=0;
        for (int j=0;j<nums.length;j++){
//            如果j号指针不等于val,就先将j号元素"覆盖"i号元素再移动i指针
            if (nums[j]!=val){
                nums[i]=nums[j];
                i++;
            }
        }
        return i;
    }

时间复杂度:O(n)

思路二:优化双指针法 思路一有一个很明显的弊端,当数组元素和val相等的元素很少时,依然需要移动很多数组元素,例如{[1,2,3,4,5],val=4}这组输入中"1,2,3"并不需要移动,但是依然会进行自身赋值操作;亦或是{[1,2,3,4,5],val=1}只需要将5覆盖到1的位置即可,但是思路一的算法并不是这样的。

真对上述出现的问题,对思路一进行优化:1. 双指针i和j分别等于0和nums.length。2. 当i指向的元素等于val时,就先让j-1指向的元素覆盖i指向的元素再进行j–移动。3. 如果i指向元素不等于val,就进行i++移动。4. 循环直至i>=j。

public int removeElement(int[] nums, int val) {
    if (nums==null||nums.length==0)return 0;
    int i=0,j=nums.length;
    while (i<j){
        if (nums[i]==val){
            nums[i]=nums[j-1];
            j--;
        }else {
            i++;
        }
    }
    return j;
}

时间复杂度:O(n),i和j最多遍历j步。在这个方法中,赋值操作的次数等于要删除的元素的数量。因此,如果要移除的元素很少,效率会更高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值