/秋招突击——7/21——复习{堆——数组中的第K大元素}——新作{回溯——全排列、子集、电话号码的字母组合、组合总和、括号生成}

news2024/9/20 21:48:51

文章目录

    • 引言
    • 复习
      • 数组中的第K大的最大元素
        • 复习实现
        • 参考实现
    • 新作
      • 回溯模板
      • 46 全排列
        • 个人实现
        • 参考实现
      • 子集
        • 个人实现
        • 参考实现
      • 电话号码的字母组合
        • 复习实现
      • 组合总和
        • 个人实现
        • 参考实现
      • 括号生成
        • 复习实现
    • 总结

引言

  • 昨天的科大讯飞笔试做的稀烂,今天回来好好练习一下,主要是针对下述两种题型,分别是对顶堆还有回溯,对顶堆的题目并不多,主要是回溯。下次再遇到这种题目,直接背模板,然后开始做!

复习

数组中的第K大的最大元素

  • 题目链接

  • 第一次做

  • 第二次做

  • 不知不觉已经是第三次做了,感觉还是有点懵,O(N)的时间复杂度,说明可以遍历多次,但是不能嵌套遍历!想想看哈

复习实现
  • 我还是会使用堆实现,并且发现了如果第一次不会做,那么后续会一直不会做,记不住!这里还是要总结.
  • 这里还是使用了堆排序时间,虽然时间复杂度不满足要求,但是单纯为了练习一下!
class Solution {
    public int findKthLargest(int[] nums, int k) {
        //define m is lenght ,and pq to sort the num
        int m = nums.length;
        Queue<Integer> pq = new PriorityQueue<>();

        // traverse the nums
        for(int i = 0;i < m;i ++){
            if(pq.size() < k)   pq.add(nums[i]);
            else{
                if(nums[i] > pq.peek()){
                    pq.poll();
                    pq.add(nums[i]);
                }
            }
        }

        return pq.peek();
        
    }
}

在这里插入图片描述

参考实现
  • 这里正确的做法是使用快排进行修改,这里先回顾一下快排的模板
void quickSort(nums q,int l ,int r){
	if(l >= r) return;
	
	int i = l - 1,j = r + 1,x = q[(l + r)>>1];
	while(i< j){
		do i ++ ;while(q[i] < x);
		do j ++ ;while(q[j] > x);
		if(i < j) swap(q[i],q[j]);
	}
	quickSort(q,l,j),quickSort(q,j + 1,r);
}
  • 这样背!虽然很蹩脚,但是能记住就行了,记住了就好些了!
    • 左右相交就返回
    • 左左右右是 ij
    • i加小 j减大
    • i小j大做交换
    • j做划分两边排

这里是要求第K大的数字,所以得改变一下i和j交换的方向,最后的序列应该是从大到小,然后再是找第k大的元素,这道题是记住了!修改的话,就从最后的终止条件开始!

具体实现

class Solution {

    public int quickSort(int[] nums,int l ,int r,int k){
        if(l == r)  return nums[k];

        int i = l - 1,j = r + 1,x = nums[(l + r) >> 1];
        while(i < j){
            do i ++;while(nums[i] > x);
            do j --;while(nums[j] < x);
            if(i < j) {
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
        if(j >= k)   return quickSort(nums,l,j,k);
        else return quickSort(nums,j + 1,r,k);
    }

    public int findKthLargest(int[] nums, int k) {
        //define m is lenght ,and pq to sort the num
        int m = nums.length;

        // traverse the nums
        int x = quickSort(nums,0,m - 1,k - 1 );
    
        return x;
        
    }
}

在这里插入图片描述

新作

回溯模板

void dfs(int[] nums,int idx){
	// 终止条件
	if(idx == termination){
		// 目标操作
		return;
	}

	//迭代内容
	for(){
		dfs(nums,idx + 1);
		// 恢复现场
	}
}
		
  • 这里要确定两个东西,一个是总的迭代对象,还有一个是单次迭代的修改内容,下面把下面几个题按照这个模板都分析一下!

  • 全排列

    • n个对象排在n个位置,每一个位置都要迭代一次,然后每一次都要从剩下没有排的对象中选出来的,所以
      • 总的迭代次数:n个位置,终止条件就是迭代n次
      • 单次迭代内容:在可选的选项中随机选择一个。
  • 子集

    • n个对象,其中选择任意一个有几种选择方法,遍历每一个元素,然后根据每一个元素决定是否选中,所以
      • 总的迭代次数:n个对象,终止条件所有元素都决策过了。
      • 单次迭代内容:当前元素是否选中两种情况,选中当前元素,不选中当前元素,
  • 组合总和

    • n个对象,选择其中若干个若干次,形成目标值,所以
      • 总的迭代次数:n个元素,每一个元素都要遍历
      • 单次迭代内容:当前元素选择零次,或者若干次

其实如果能够从树的角度分析,效果会更好,树的深度就是总的迭代次数,单个节点的子节点数也就是树的宽度,就是单次迭代需要考虑的内容

46 全排列

  • 题目链接
    在这里插入图片描述
    注意
  • 所有整数互不相同,不用处理特殊情况。
  • 数组的长度会出现的一的情况,边界情况,需要特殊处理!
个人实现
  • 标准回溯,确定一个模板直接开始写。
    • 终止条件:idx = 0,并将结果加入到res中
    • 迭代条件:遍历剩余的元素,随机加入到临时列表中
class Solution {

    public List<List<Integer>> res = new ArrayList<>();

    void dfs(int[] nums,int idx,List<Integer> list,Set<Integer> set){
        if(idx == 0){
            res.add(new ArrayList(list));
            return;
        }

        // iterator condition
        for(int i = 0;i < nums.length;i ++){
            int x = nums[i];
            if(!set.contains(x)){
                list.add(x);
                set.add(x);
                dfs(nums,idx - 1,list,set);
                list.remove(list.size() - 1);
                set.remove(x);
            }
        }
    }
    public List<List<Integer>> permute(int[] nums) {
        List<Integer> list = new ArrayList<>();
        Set<Integer> set = new HashSet<>();
        dfs(nums,nums.length,list,set);
        return res;
    }
}

觉得写的有点繁琐,看看参考的教程是怎么写的

  • 注意,在Java中res.add方法是引用传递,需要创建一个同元素变量的副本才行,不然会越界!
参考实现
  • 明确需要记录的状态

    • 每一个位置具体的位置保存的数字,也就是list
    • 每一个数字的使用情况,使用set
    • 递归到了第几步
  • 他是使用全局变量来声明,没有使用形参传递对应

这里就不写了,基本上都是一致的

子集

  • 题目链接
    在这里插入图片描述
    注意
  • 存在数组为1的特殊情况,可能需要特殊处理
  • 各个元素互不相同
  • 元素有负数的情况
个人实现
  • 刚才那道题目是所有的排列情况,这道题目是所有的组合情况,应该也可以使用回溯实现。这个和刚才相同,不过是在每一次的改变环境的时候,就将结果进行保存!
class Solution {
    
    List<Integer> list = new ArrayList<>();
    Set<Integer> set = new HashSet<>();
    Set<List<Integer>> res = new HashSet<>();

    void dfs(int[] nums,int idx){
        if(idx == nums.length){
             List<Integer> temp = new ArrayList<>(list);
                Collections.sort(temp);
                res.add(temp);
        }

        for(int i = 0;i < nums.length;i ++){
            int x = nums[i];
            if(!set.contains(x)){
                set.add(x);
                list.add(x);
                List<Integer> temp = new ArrayList<>(list);
                Collections.sort(temp);
                res.add(temp);
                dfs(nums,idx + 1);
                set.remove(x);
                list.remove(list.size() - 1);
            }
        }
    }

    public List<List<Integer>> subsets(int[] nums) {
        res.add(Arrays.asList());
        dfs(nums,0);
        List<List<Integer>> resList = new ArrayList<>();
        for(List<Integer> x:res){
            resList.add(x);
        }
        return resList;
    }
}

在这里插入图片描述

上面这样做就不对,得再想想,如果是回溯的话,得更新一下状态的改变,不能直愣愣的添加对应的元素!出来结果了,然后再添加!

  • 好蠢呀,没想到,没想到,既然没想到,记下来,下次肯定能够想到!
参考实现

方法一、DFS
递归的条件

  • 每一个元素只有两种情况,放或者不放,所以遍历这两种情况就行了!
class Solution {
    
    List<Integer> list = new ArrayList<>();
    List<List<Integer>> res = new ArrayList<>();

    void dfs(int[] nums,int idx){
        // termiante condition
        if(idx == nums.length){
            res.add(new ArrayList(list));
            return ;
        }

        // traverse all the condition
        // get  the idx num
        list.add(nums[idx]);
        dfs(nums,idx + 1);
        list.remove(list.size() - 1);

        // do not get the idx num
        dfs(nums,idx + 1);
    }


    public List<List<Integer>> subsets(int[] nums) {
        dfs(nums,0);
        return res;
    }
}

在这里插入图片描述

方法二、位运算

  • 将这个问题转化为对应的二进制表示,每一个物体只有放或者不放两种情况,对应就是不同的二进制数,而且全排列的最终结果数量就是 2 n 2^n 2n

具体实现如下
这里刚好练习一下Java中的二进制数是怎么操作的!

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        int n = nums.length;

        // traverse all the binary num
        for(int i = 0;i < (1 << n);i ++){
            List<Integer> temp = new ArrayList<>();
            for(int j = 0;j < n;j ++){
                // judge the j is 0 or 1
                if(((i >> j) & 1) == 1){
                    temp.add(nums[j]);
                }
            }
            res.add(temp);
        }

        return res;
       
    }
}

电话号码的字母组合

  • 题目链接
  • 第一次做
复习实现
class Solution {
    Map<Character,List<Character>> map = new HashMap<>();
    StringBuilder str = new StringBuilder();
    List<String> res = new ArrayList<>();

    void dfs(String digits,int idx){
            if(idx == digits.length()){
                //System.out.println(str.toString());
                res.add(str.toString());
                return;
            }

            for(char x:map.get(digits.charAt(idx))){
                str.append(x);
                dfs(digits,idx + 1);
                str.deleteCharAt(str.length() - 1);
            }

        }

    public List<String> letterCombinations(String d) {
        map.put('2',Arrays.asList('a','b','c'));
        map.put('3',Arrays.asList('d','e','f'));
        map.put('4',Arrays.asList('g','h','i'));
        map.put('5',Arrays.asList('j','k','l'));
        map.put('6',Arrays.asList('m','n','o'));
        map.put('7',Arrays.asList('p','q','r','s'));
        map.put('8',Arrays.asList('t','u','v'));
        map.put('9',Arrays.asList('w','x','y','z'));    
        if(d.length() == 0) return res;
        dfs(d,0);
        return res;
    }
}

在这里插入图片描述

  • 没以前使用C++实现起来那么快,写起来也没有那么方便!

组合总和

  • 题目链接
    在这里插入图片描述
    注意
  • 所有元素互不相同
  • 每一个元素可以放很多次
个人实现
  • 这题可以使用两种方式实现
    • 完全背包问题,不过需要记录对应的背包状态,时间复杂度比较低,但是不知道怎么记录满足条件的状态
      • 随便选一个,装满为止;F-V,加上价值,这里价值为零
    • 暴力回溯,时间复杂度高

暴力回溯

class Solution {
    
    List<Integer> list = new ArrayList<>();
    List<List<Integer>> resList = new ArrayList<>();
    Set<List<Integer>> res = new HashSet<>();


    // brute dfs to solve the problem
    void dfs(int[] candi,int tar,int temp){
        if(temp == tar){
            List<Integer> tempList = new ArrayList(list);
            Collections.sort(tempList);
            res.add(tempList);
            return;
        }

        for(int i = 0;i < candi.length;i ++){
            if(temp + candi[i] <= tar){
                // put
                //System.out.println(candi[i]);

                list.add(candi[i]);
                dfs(candi,tar,temp + candi[i]);
                list.remove(list.size() - 1);
            }
        }

    }
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        dfs(candidates,target,0);
        for(List<Integer> x:res){
            resList.add(x);
        }
        return resList;
    }
}

在这里插入图片描述
我靠,这个居然能过,也是离谱了!

完全背包问题

class Solution {
    
    List<Integer> list = new ArrayList<>();
    List<List<Integer>> resList = new ArrayList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        int[] f = new int[target + 1];

        f[0] = 1;
        for(int i = 0;i < candidates;i ++){
            for(int j = candidates[i];j < target;j ++){
                f[j] = f[j] + f[j - candidates[i]];
            }
        }
       
        return f[target - 1];
    }
}
  • 这里只能写成这样,因为我并不知道怎么保存中间状态!

不能用完全背包,完全背包并不能获取中间状态!!

参考实现
  • 只能说我对于的回溯的理解还是不够深刻,两种存放方式
    • 是否放当前的数字,要用深度u控制,防止出现死循环
    • 当前物体一定要放,但是顺序不同,需要set控制是否出现
class Solution {
    
    List<Integer> list = new ArrayList<>();
    List<List<Integer>> resList = new ArrayList<>();

    void dfs(int[] candidates,int dpt,int tar){
        if(tar == 0){
            resList.add(new ArrayList(list));
            return;
        }

        if(dpt == candidates.length)    return;

        for(int i = 0;i * candidates[dpt] <= tar ;i ++){
            dfs(candidates,dpt + 1,tar - i * candidates[dpt]);
            list.add(candidates[dpt]);
        }    

        for(int i = 0; i * candidates[dpt] <= tar ;i ++)
            list.remove(list.size() - 1);

    }

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        dfs(candidates,0,target);
        return resList;
    }
}

在这里插入图片描述
实现起来确实更优!如果放或者不放,还是需要使用的深度进行控制!
无论怎么样,都需要加上的对应idx控制

括号生成

  • 题目链接
  • 第一次学习链接
复习实现
class Solution {
    
    // define the structure to store the result
    List<String> res = new ArrayList<>();
    StringBuilder str = new StringBuilder();

    void dfs(int idx,int n,int l,int r){
        if(idx == 2 * n && l == r){
            if(l == r)
                res.add(str.toString());
            return ;
        }

        // remove the special situation
        if(r > l  || l > n || r > n)  return;
          
        str.append('(');
        dfs(idx + 1,n,l + 1,r);
        str.deleteCharAt(str.length() - 1);
        str.append(')');
        dfs(idx + 1,n,l ,r + 1);
        str.deleteCharAt(str.length() - 1);
       
    }

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

在这里插入图片描述

上一次写的真丝滑!

vector<string> res;
void dfs(int n,int lc,int rc,string s){
    /*
     * n表示括号数量,lc表示左括号数量,rc表示右括号数量,s表示字符串
     */
    // 判定什么时候加左括号
    if (lc == n && rc == n) res.push_back(s); 
    else{
        // 什么时候加右括号
        if (lc < n) dfs(n,lc + 1,rc,s + "(");
        if (lc > rc && rc < n) dfs(n,lc,rc + 1,s + ")");
    }
}

vector<string> generateParenthesis(int n){
    dfs(n,0,0,"");
    return res;
}

总结

  • 今天这几道题做完了,算是对于深度有了更加深刻的认识!最好能够画出对应的树形结构,树的高度就是总的迭代次数,树的宽度就是单次迭代需要迭代的内容!
  • 写回溯,还是比写其他算法要轻松很多!
  • 写回溯,一定要画图!写算法一定要画图,转成对应的数据结构!回溯就是可以转成对应的树形结构!
  • 一定要要记得恢复现场,每一步都要恢复现场,因为你的编程习惯是共用一个StringBuilder。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1940253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git实操之线上分支合并

线上分支合并 【 1 】本地dev分支合并到本地master上 # 本地dev分支合并到本地master上# 远程(线上)分支合并# 本地dev分支合并到本地master上# 远程(线上)分支合并#####本地和线上分支同步################ #### 远程创建分支&#xff0c;拉取到本地####-远程创建分支&#…

服务攻防-应用协议cve

Cve-2015-3306 背景&#xff1a; ProFTPD 1.3.5中的mod_copy模块允许远程攻击者通过站点cpfr和site cpto命令读取和写入任意文件。 任何未经身份验证的客户端都可以利用这些命令将文件从文件系统的任何部分复制到选定的目标。 复制命令使用ProFTPD服务的权限执行&#xff0c;…

《2024 年 7 月 17 日最新开发者服务 API 推荐》

在当今的数字货币领域&#xff0c;对代币持有信息的精准洞察至关重要。而 Bitquery 代币持有信息查询 API 接口的出现&#xff0c;为开发者和投资者提供了强大的工具。无论是想要揭示代币趋势&#xff0c;检测虚假交易&#xff0c;发现热门代币&#xff0c;还是评估代币财富差距…

查找算法③-斐波那契查找算法/黄金分割查找算法

一、算法原理 斐波那契查找算法又称黄金分割查找算法&#xff0c;它是在二分查找基础上根据斐波那契数列进行分割的一种衍生算法&#xff0c;简单来说&#xff0c;二分查找是一分为二进行查找&#xff0c;斐波那契查找是使用斐波那契数列进行分割查找。而斐波那契数列就是我们通…

【Dison夏令营 Day 26】PyGame 中的赛车游戏

在本文中&#xff0c;我们将了解如何使用 Pygame 在 Python 中创建一个赛车游戏。在这个游戏中&#xff0c;我们将拥有驾驶、障碍物碰撞、通过关卡时速度增加、暂停、倒计时、记分牌和说明书屏幕等功能。 所需模块&#xff1a; 在继续之前&#xff0c;请在命令提示符下运行以下…

百科词条可以删除吗?删除百科词条的方法

大多时候大家都是想创建百度词条&#xff0c;然而有时候也会需要删除某些词条&#xff0c;因为其内容有错误、不实或者涉及某些敏感信息。但是百科词条删除需要非常明确的理由&#xff0c;不然也是很难通过的&#xff0c;这里小马识途百科顾问先初步分享下删除百科词条的流程。…

一套C#语言开发的医学影像归档与通讯系统PACS源码,三甲以下医院都能满足

医学影像归档与通讯系统&#xff08;PACS&#xff09;系统&#xff0c;是一套适用于从单一影像设备到放射科室、到全院级别等各种应用规模的医学影像归档与通讯系统。PACS集患者登记、图像采集、存档与调阅、报告与打印、查询、统计、刻录等功能为一体&#xff0c;有效地实现了…

Logstash docker发布

一 下载Logstash 不废话了&#xff0c;我下载的7.17.6 二 新增配置文件 在logstash/pipeline中&#xff0c;添加logstash.conf input {jdbc { # 连接jdbc_connection_string > "jdbc:mysql://192.168.1.1:3306/kintech-cloud-bo&#xff1f;characterEncodingUTF-8&…

【Linux网络】套接字编程

本篇博客整理了 socket 套接字编程的相关内容&#xff0c;包括 socket 网络通信原理、socket 相关的系统调用接口等&#xff0c;分别演示了基于UDP协议、TCP协议的 socket 网络编程&#xff0c;旨在让读者更加深入理解网络通信原理和设计&#xff0c;对网络编程有初步的认识和掌…

ECCV2024中有哪些值得关注的扩散模型相关的工作?

Diffusion Models专栏文章汇总:入门与实战 The Fabrication of Reality and Fantasy: Scene Generation with LLM-Assisted Prompt Interpretation 本文探讨了如何利用扩散模型生成需要艺术创造力或专业知识的复杂和富有想象力的图像提示。提出了一个新颖的评估框架RealisticF…

VulnHub:insomnia

靶机下载地址 信息收集 主机发现和端口扫描 攻击机网段192.168.31.0/24。 # 主机发现 nmap 192.168.31.0/24 -Pn -T4 # 靶机ip:192.168.31.207 端口扫描 nmap 192.168.31.207 -A -p- -T4 经过nmap扫描发现目标主机有http服务&#xff0c;端口是8080。 目录扫描 访问http…

Android RSA 加解密

文章目录 一、RSA简介二、RSA 原理介绍三、RSA 秘钥对生成1. 密钥对生成2. 获取公钥3. 获取私钥 四、PublicKey 和PrivateKey 的保存1. 获取公钥十六进制字符串1. 获取私钥十六进制字符串 五、PublicKey 和 PrivateKey 加载1. 加载公钥2. 加载私钥 六、 RSA加解密1. RSA 支持三…

YOLOv2小白精讲

YOLOv2是一个集成了分类和检测任务的神经网络&#xff0c;它将目标检测和分类任务统一在一个单一的网络中进行处理。 本文在yolov1的基础上&#xff0c;对yolov2的网络结构和改进部分进行讲解。yolov1的知识点可以看我另外一篇博客&#xff08;yolov1基础精讲-CSDN博客&#xf…

MySQL的索引、事务

MySQL的索引 索引的概念 索引是一个排序的列表&#xff0c;在列表当中存储索引的值以及索引值对应数据所在的物理行。 索引值和数据是一一映射的关系。 索引的作用 使用索引之后就不需要扫描全表来定位某行的数据 加快数据库查询的速度。 索引可以是表中的一列也可以是多…

Dify中接入GPT-4o mini模型

GPT-4o mini模型自己承认是基于GPT-3.5架构的模型&#xff0c;有图有真相&#xff1a; 一.GPT-4o mini官网简介 GPT-4o mini&#xff08;“o"代表"omni”&#xff09;是小型型号类别中最先进的型号&#xff0c;也是OpenAI迄今为止最便宜的型号。它是多模态的&#x…

C++11: auto 关键字

目录 **前言****1. 推导规则****2. 不能使用 auto 的场景****3. 常见的使用场景** 前言 在 C11 以前&#xff0c;auto 关键字的语义继承自 C 语言&#xff0c;表示 进入块后&#xff0c;自动分配内存&#xff0c;即分配堆栈内存。也就是说 auto 只能用于函数内&#xff0c;然而…

昇思25天学习打卡营第14天|基于MindNLP+MusicGen生成自己的个性化音乐

MusicGen是由Meta AI的团队开发出的一种音乐生成模型&#xff0c;它用一个语言模型来根据文本描述或音频提示制作音乐。这个模型分三步工作&#xff1a;首先&#xff0c;把用户给的文本转换成一系列状态&#xff1b;然后&#xff0c;用这些状态来预测音乐的音频token&#xff1…

[HTML]一文掌握

背景知识 主流浏览器 浏览器是展示和运行网页的平台&#xff0c; 常见的五大浏览器有 IE浏览器、火狐浏览器&#xff08;Firefox&#xff09;、谷歌浏览器&#xff08;Chrome&#xff09;、Safari浏览器、欧朋浏览器&#xff08;Opera&#xff09; 渲染引擎 浏览器解析代码渲…

【Linux 15】进程间通信的方式 - 管道

文章目录 &#x1f308; 一、管道介绍&#x1f308; 二、匿名管道⭐ 1. 匿名管道的概念⭐ 2. 匿名管道的创建⭐ 3. 匿名管道的本质⭐ 4. 匿名管道的使用⭐ 5. 匿名管道的特点⭐ 6. 匿名管道的大小 &#x1f308; 三、命名管道⭐ 1. 命名管道的概念⭐ 2. 命名管道的创建⭐ 3. 命…

USB转多路UART - USB 基础

一、 前言 断断续续做了不少USB相关开发&#xff0c;但是没有系统去了解过&#xff0c;遇到问题就很被动了。做这个USB转UART的项目就是&#xff0c;于是专门花了一天的时间学习USB及CDC相关&#xff0c;到写这文章时估计也忘得差不多了&#xff0c;趁项目收尾阶段记录一下&am…