刷题28-30(力扣0322/0078/0221)

0322. 零钱兑换 

题目:

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。

基本思路:将金币从大到小开始排列,先拿最大的,再拿后面次大的,以此类推。【失败】

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        sort(coins.begin(),coins.end());
        int size=coins.size();
        if(amount==0) return 0;
        int ans=0;int remains=amount;
        for(int i=size-1;i>=0;i--){
            while(remains>=coins[i]){
                remains-=coins[i];
                ans++;
                
            }
        }
        return (remains>0)?-1:ans;
    }
};

但显然有bug,思考一顿发现,他可能不需要最后一个,可以是倒数第二个拼起来 ,例如上面这个例子3+4可以出现7但运行时依然把5放上了:

所以进行改进: 如果再进行一层遍历,便从哪一个开始,显然复杂度太高。

这个时候想到了,动态规划

创建了一个长度为 amount+1 的数组 dp,dp[i] 表示凑齐金额 i 所需的最少硬币数目。然后通过迭代更新 dp 数组,最终返回 dp[amount]。如果 dp[amount] 大于 amount,则表示无法凑出总金额,返回 -1。

代码:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
                
        vector<int> dp(amount + 1, amount + 1);
        dp[0] = 0; // if(amount==0)return 0;

        for (int i = 1; i <=amount; i++) {
            for (int j = 0; j < coins.size(); j++) {
                if (coins[j] <= i)//硬币数值小于总数
                    dp[i] = min(dp[i],dp[i-coins[j]]+1);//可以选择放还是不放。
            }
        }
        return dp[amount]>amount?-1:dp[amount];
    }
};

每一次dp都可以选择放还是不放。不放就是dp[i]=dp[i];放的话,首先数量要+1,谁的数量呢?是(当前存放金钱总额-放入的金币额 )所需的数量加1。

vector<int> dp(amount + 1, amount + 1);

创建了一个名为 dp 的整型向量(vector),并初始化该向量的大小为 amount + 1,所有元素的初始值都设置为 amount + 1

具体来说,这行代码创建了一个大小为 amount + 1 的整型向量 dp,并且用 amount + 1 来填充所有的元素。在动态规划问题中,通常会使用类似这样的数组来记录中间状态或者保存子问题的解,以便后续计算。在这个特定的动态规划问题中,dp[i] 代表凑齐金额 i 所需的最少硬币数量,初始值设为 amount + 1 也是为了确保后续更新时能正确比较和更新数值。

本题over


78. 子集

题目:

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

思路:这道题自己想的就是遍历。思考用两层for循环,控制左右界限,放之间的元素。后来的时候,又看到一个题解

遍历枚举:

大概意思是:最外层遍历数组的元素,内层循环:复制上一步的子集,然后将当前元素加到复制的子集里面 ,构成新的子集。

举个例子:比如{1,2,3} :
  • 我先产生{1}的子集--》ans={{},{1}},
  • 我复制这个子集subset={{},{1}};此时数组元素遍历到‘2’;
  • 把‘2‘’这个元素加到subset里面subset={{2},{1,2}};
  • 把subset’这个元素加到ans里面此时ans={{},{1},{2},{1,2}}
  • 3同理  1)subset={{},{1},{2},{1,2}};2)subset={{3},{1,3},{2,3},{1,2,3}}
  • 3)ans={{},{1},{2},{1,2},{3},{1,3},{2,3},{1,2,3}}
代码 
class Solution {
public:
    // 生成一个整数数组的所有可能子集
    vector<vector<int>> subsets(vector<int>& nums) {
        // 初始化结果集,包含空集
        vector<vector<int>> ans = {{}};
        
        // 遍历原数组中的每个元素
        for (int num : nums) {
            // 获取当前结果集的大小
            int n = ans.size();
            
            // 遍历当前结果集中的每个子集
            for (int i = 0; i < n; ++i) {
                // 复制当前子集
                vector<int> subset = ans[i];
                
                // 将当前元素加入子集中
                subset.push_back(num);
                
                // 将更新后的子集加入结果集
                ans.push_back(subset);
            }
        }
        
        // 返回生成的所有子集
        return ans;
    }
};

二进制位法 

还有使用二进制位来编写的,看了半天没看懂,后来求助解释一下吧

  • 对于长度为 n 的原始数组,通过遍历 0 到 2^n - 1 的所有数字,可以表示所有可能的子集。
  • 在内层循环中,根据当前数字 i 的二进制表示中的 1,将对应位置的元素加入当前子集。
  • 最终将每个生成的子集添加到结果集中,得到所有可能的子集
举个例子
当我们具体遍历生成子集的过程时,以数组 {3,2,1} 为例:

当 i = 0 时,二进制表示为 000,对应空集 {}。

内层循环不执行,因为没有任何元素被选中。
当 i = 1 时,二进制表示为 001,对应子集 {3}。

内层循环执行,检查第 0 位是否为 1,发现是,将 nums[0](即 3)加入到当前子集中。
当 i = 2 时,二进制表示为 010,对应子集 {2}。

内层循环执行,检查第 1 位是否为 1,发现是,将 nums[1](即 2)加入到当前子集中。
当 i = 3 时,二进制表示为 011,对应子集 {2, 3}。

内层循环执行,检查第 0 和第 1 位是否为 1,发现都是,将 nums[0] 和 nums[1] 加入到当前子集中。
当 i = 4 时,二进制表示为 100,对应子集 {1}。

内层循环执行,检查第 2 位是否为 1,发现是,将 nums[2](即 1)加入到当前子集中。
当 i = 5 时,二进制表示为 101,对应子集 {1, 3}。

内层循环执行,检查第 0 和第 2 位是否为 1,发现第 0 位和第 2 位为 1,将 nums[0] 和 nums[2] 加入到当前子集中。
当 i = 6 时,二进制表示为 110,对应子集 {1, 2}。

内层循环执行,检查第 1 和第 2 位是否为 1,发现第 1 位和第 2 位为 1,将 nums[1] 和 nums[2] 加入到当前子集中。
当 i = 7 时,二进制表示为 111,对应子集 {1, 2, 3}。

内层循环执行,检查所有位都为 1,将所有元素 {1, 2, 3} 加入到当前子集中。
通过这样的遍历过程,我们可以逐步生成出数组 {1, 2, 3} 的所有可能子集。
代码: 
class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ans; // 存储所有可能的子集

        int n = nums.size(); // 原始数组的长度

        // 遍历所有可能的子集长度
        for (int i = 0; i < (1 << n); i++) {
            vector<int> subset; // 当前子集

            // 对于每个子集长度,遍历原始数组
            for (int j = 0; j < n; j++) {
                // 检查第 j 位是否在当前子集中
                if (i & (1 << j)) {
                    subset.push_back(nums[j]); // 将元素加入当前子集
                }
            }

            ans.push_back(subset); // 将当前子集加入结果集
        }
        
        return ans;
    }
};
 其实这道题最最重要的是回溯思想!!
  • .  -. - 力扣(LeetCode)总结了回溯问题类型,带你搞懂回溯算法(大量例题)
  • 看这个题解就可以

本题over

221. 最大正方形

思想:动态规划

我们可以使用动态规划来解决这个问题。以

假设我们有一个二维矩阵 matrix,其中每个元素是 '0' 或 '1'。我们定义一个额外的二维数组 dp 来存储在以当前位置为右下角的情况下的最大正方形边长。

  1. 首先,将 dp 数组初始化为与原始矩阵相同大小,类型为整数。将第一行和第一列直接复制为原始矩阵的值(因为在边界上无法形成正方形)。

  2. 对于其余位置 (i, j),如果 matrix[i][j] 是 '1',则考虑它左侧、上方和左上方三个位置的 dp 值,取它们的最小值并加 1,dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1

  3. 在更新 dp 的过程中,记录下出现的最大边长值,即可得到最大正方形的边长。

  4. 最后,返回最大正方形的面积,即边长的平方。

这样,通过动态规划算法,我们可以高效地找到给定矩阵中的最大正方形的边长。

dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1。
如图,当dp[i][j] 来存储在以当前位置[i][j]为右下角的情况下的最大正方形边长时,
dp的值就与上[i-1][j] 左[i][j-1] 左上[i-1][j-1]三个位置的dp相关。看图可以发现与他们最小值相关。
为什么要加1,因为当前位置计算的话,可定是1,可以构成一个小正方形

图解 

代码如下:一定要分清行列
class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int n = matrix.size();
        int m = matrix[0].size();
        if (m == 0 || n == 0)
            return 0;
        int max_len = 0;
        vector<vector<int>> dp(n, vector<int>(m));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (matrix[i][j] == '1') {
                    if (i == 0 || j == 0) {//第一行第一列dp值=matrix值
                        dp[i][j] = 1;
                    } else {
                        dp[i][j] = min({dp[i - 1][j], dp[i][j - 1],dp[i - 1][j - 1]}) +1;
                    }
                    max_len = max(max_len, dp[i][j]);
                }
            }
        }

        return max_len * max_len;
    }
};

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LLM - 大语言模型的分布式训练 概述

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/136924304 大语言模型的分布式训练是一个复杂的过程&#xff0c;涉及到将大规模的计算任务分散到多个计算节点上。这样做的目的是为了处…

java面试:常见的限流算法有哪些

1 什么是限流算法 限流算法是一种用于限制流量请求的频率或速率的算法&#xff0c;其目的是在高并发或大流量请求的情况下&#xff0c;保护系统服务的安全性和可用性。限流算法可以应对热点业务带来的突发请求、调用方bug导致的突发请求以及恶意攻击请求等情况。是一种系统保护…

10W字解析 SpringBoot技术内幕文档,实战+原理齐飞,spring事务实现原理面试

第3章&#xff0c;Spring Boot构造流程源码分析&#xff0c;Spring Boot的启动非常简单&#xff0c;只需执行一个简单的main方法即可&#xff0c;但在整个main方法中&#xff0c;Spring Boot都做了些什么呢&#xff1f;本章会为大家详细讲解Spring Boot启动过程中所涉及的源代码…

《深入解析 C#》—— C# 3 部分

文章目录 第三章 C#3&#xff1a;LINQ及相关特性3.1 自动实现属性&#xff08;*&#xff09;3.2 隐式类型 var&#xff08;*&#xff09;3.3 对象和集合初始化3.3.1 对象初始化器3.3.2 集合初始化器 3.4 匿名类型3.4.1 基本语法和行为3.4.2 编译器生成类型3.4.3 匿名类型的局限…

Linux信号补充——信号捕捉处理

一、信号的捕捉处理 ​ 信号保存后会在合适的时间进行处理&#xff1b; 1.1信号处理时间 ​ 进程会在操作系统的调度下处理信号&#xff0c;操作系统只管发信号&#xff0c;即信号处理是由进程完成的&#xff1b; ​ 1.信号处理首先进程得检查是否有信号&#xff1b;2.进程…

双指针(对撞指针、快慢指针)

本博客将讲述OJ题中的常用的双指针 双指针的含义 双指针算法是一种常用的算法技巧&#xff0c;它通常用于在数组或字符串中进行快速查找、匹配、排序或移动操作。 双指针并非真的用指针实现&#xff0c;一般用两个变量来表示下标&#xff08;在后面都用指针来表示)。双指针算…

QML TextField 默认无法鼠标选中内容

1.import QtQuick.Controls 2.0 后的TextField默认无法选中内容如下图&#xff1a; 2.增加属性设置 selectByMouse: true 可以选中内容了 TextField{ selectByMouse: true text:"1234567890987654321" } 效果如下:

多线程(JUC, ReentrantLock, 原子类, 线程池, 信号量 Semaphore, CountDownLatch)

JUC Java.util.concurrent 包, 存放了并发编程相关的组件, 目的是更好的支持高并发任务 (多线程只是实现并发编程的一种具体方式 …) ReentrantLock 可重入互斥锁, 和 synchronized 定位类似, 用来实现互斥效果, 保证线程安全. synchronized 对对象加锁, 保护临界资源Reentreat…

面向量产!基于视觉的速度距离估计

面向量产&#xff01;基于视觉的速度距离估计 论文名称&#xff1a;Vision-based Vehicle Speed Estimation: A Survey 导读 在精确检测车速车距的方案中&#xff0c;视觉方案是非常具有挑战性的&#xff0c;但由于没有昂贵的距离传感器而大幅降低成本&#xff0c;所以潜力巨…

【现代C++】范围基于的for循环

现代C中的范围基于的for循环&#xff08;range-based for loop&#xff09;是C11引入的一项特性&#xff0c;旨在简化对容器或范围的迭代过程。这种循环语法不仅使代码更清晰易读&#xff0c;还减少了迭代时的错误。以下是范围基于的for循环的详细介绍&#xff1a; 1. 基本用法…

Vue3的与2的简单区别

Vue2选项式api Vue3组合式API setup方法的使用&#xff0c;最后需要return setup语法糖省略了内部的export default{} 和return 内容 以及组件的注册 reactive生成响应式对象&#xff0c;只能适用于复杂对象&#xff0c;简单类型不可 ref生成响应式数据&#xff1a;复杂类型和简…

leetcode 数组练习,美团优选面试题java

public int maxSubArray(int[] nums) { int countnums[0]; int resnums[0]; for(int i1;i<nums.length;i){ if(count<0){ countnums[i]; }else{ countnums[i]; } resMath.max(res,count); } return res; } 3、两数之和 利用map,来存储数组值和当前位置&…

【Review】电动汽车百人会

汽车强国靠四化--电动化、智能化、低碳化、全球化。 1.坚持电动化&#xff1a;电动化是经过二十多年反复论证的既定战略和技术路线、不能动摇、无需改变、要将电动化进行到底&#xff0c;全力攻克下一代电动化核心技术--全固态锂电池;市场方面要采用“双轮”驱动战略一方面继续…

基于PID控制器的四旋翼无人机控制系统的simulink建模与仿真,并输出虚拟现实动画

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1四旋翼无人机的动力学模型 4.2 PID控制器设计 4.3 姿态控制实现 4.4 VR虚拟现实动画展示 5.完整工程文件 1.课题概述 基于PID控制器的四旋翼无人机控制系统的simulink建模与仿真,并输出vr虚拟现实…

政安晨:【深度学习实践】【使用 TensorFlow 和 Keras 为结构化数据构建和训练神经网络】(四)—— 过拟合和欠拟合

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 通过增加容量或提前停止来提高性能。 在深度学习中&#…

Springboot 整合 Knife4j (API文档生成工具)

目录 一、Knife4j 介绍 二、Springboot 整合 Knife4j 1、pom.xml中引入依赖包 2、在application.yml 中添加 Knife4j 相关配置 3、打开 Knife4j UI界面 三、关于Knife4j框架中常用的注解 1、Api 2、ApiOperation ​3、ApiOperationSupport(order X) ​4、ApiImplici…

C# WPF编程-布局

C# WPF编程-布局 布局WPF布局原则布局过程布局容器布局属性Border控件StackPanel布局WrapPanel布局DockPanel布局Grid布局UniformGrid布局Canvas布局 布局 WPF布局原则 WPF窗口只能包含单个元素。为在WPF窗口中放置多个元素并创建更贴近实用的用户界面&#xff0c;需要在窗口…

linux系统----------MySQL索引浅探索

目录 一、数据库索引介绍 二、索引的作用 索引的副作用 (缺点) 三、创建索引的原则依据 四、索引的分类和创建 4.1普通索引 4.1.1直接创建索引 4.1.2修改表方式创建 4.1.3创建表的时候指定索引 4.2唯一索引 4.2.1直接创建唯一索引 4.2.2修改表方式创建 4.2.3创建表…

根据log信息解读内核(linux-2.6.32.24)的启动流程

目录 概述 1 从bootloader 到内核部分 2 初始化cache和CPU时钟 3 获取cache和memory信息 4 初始化cache、电源管理和中断 5 初始化USB和I2C 6 网络协议初始化 7 挂载JFFS2文件系统和初始化IO 8 初始化外围device 9 Nand Flash资源分配 10 初始化网络接口 11 注册US…

一文快速掌握docker的理念和基本使用

写在文章开头 写于一个周末&#xff0c;在复盘梳理文章时候发现这一篇关于早期了解docker时记录的文档&#xff0c;仔细阅读了一下&#xff0c;为了保证文章更加清晰以便读者使用。故再次重新一次梳理一次&#xff0c;通过这篇文章&#xff0c;你将会对docker的基本理念和基础…