线性结构-链表

news2024/11/17 2:51:52

链表也是一种常用的线性数据结构,与数组不同的是,链表的存储空间并不连续,它是用一组地址任意的存储单元来存放数据的,也就是将存储单元分散在内存的各个地址上。
这些地址分散的存储单元叫做链表的节点,链表就是由一个个链表节点连结而成的。

每个链表都有一个“链表头”,通常是一个指针。对Java而言,它是链表节点对象的引用。用来存放链表中第一个节点的地址。同时,链表中最后一个节点的指针域通常会置空null,用来表示该节点是链表的最后一个节点,没有后继节点。

链表在逻辑上是连续的,但在物理上并不一定连续,链表节点可能分散在内存的各个地址上。

  • 每个链表节点都必须包含指针域,用来存放下一个节点的内存地址。数据域则用来存放节点的的数据元素。
  • 数据域可以是一个也可以是多个,由具体的需求而定。
  • 指针域的类型必须是定义的链表节点类型,或链表节点指针类型。
  • 只要获取了链表头,就可以通过指针遍历整个链表。按照指针依次访问,直到访问到最后一个节点(指针域为null)。所以获取链表头非常重要。

链表的定义

定义链表节点

链表是由链表节点构成的,因此在定义链表结构之前,要先定义链表的节点类型。

class Node {
	int data;
	Node next;
	public Node(int data) {
		this.data = data; //构造方法,在构造结点对象时将data赋值给this .data成员
	}
}

在Java中,节点类可以放到链表类文件的最后。
但在C/C++中,必须要先声明后使用,将节点声明在链表前面。定义即可在前也可以在后。
类Node包含两个成员变量:

  • data为整型的变量,是该链表节点的数据域,可以用来存放一个整数。
  • next为Node类型的引用类型变量,是该链表节点的指针域,用来指定下一个节点。

定义链表

定义完链表节点类Node,接下来我们可以定义链表类。
链表是靠节点间的指针相互关联的。只要获取了链表头就可以通过头指针遍历整个链表。
在链表类中没有必要包含该链表的所有节点,只需要定义一个head成员就足够了。

public class MyLinkedList {
	Node head = null;
	int length = 0;
    //暂未添加链表的操作函数
}
class Node {
	int data;
	Node next;
	public Node(int data) {
		this.data = data; //构造方法,在构造结点对象时将data赋值给this .data成员
	}
}

这个链表类中包含两个成员变量:

  • head是Node类型的成员,他是链表中第一个节点的引用,也就是指向第一个节点的指针。
  • length是整形变量,用来记录数组中元素的数量。

操作链表的函数可以根据需要而定。

链表的基本操作

链表的基本操作包括向链表中插入节点和从链表中删除节点,另外根据实际需要可以定义获取链表长度、销毁链表等操作。

向链表中插入节点

public boolean insertNode(int data, int index)
这个函数表示在链表的第index个位置上插入一个整形变量data节点。

  • 参数data指链表节点中的元素值而不是节点对象。因为我们定义的链表节点Node中的数据域是int类型,所以参数data也要是int类型。
  • 参数index表示要将节点插入链表中的位置,与数组元素的位置相似,我们规定index只能是[1,length+1]范围内的值。由于链表没有数组中下标访问的操作,所以不需要在意元素位置和下标的关系。


需要注意,index所指的对象是C:

  1. 创建一个节点对象B,将data值赋值给数据域。
  2. 将A的指针域赋值给B的指针域,使B指向C。
  3. 将B的地址赋值给A的指针域,使A指向B。

如果要在链表的第一个位置**index=1**上插入节点

  • 如果head==null,则链表是个空链表,此时直接将B的地址赋值给head即可。
  • 如果head!=null,则说明链表中已有节点,此时将head视为A修改修改地址即可。
public boolean insertNode(int data, int index) {
    // 向链表的第index位置上插入一个结点,结点数据为data(这里是int类型)
    if (index < 1 || index > (length + 1)) {
        // 这种情况说明插入结点的位置不合法
        System.out.println("Insert error position. index = " + index + " length = " + length);
        return false;
    }
    // 要插入结点的位置为1,这时的操作有些特殊
    if (index == 1) {
        if (head == null) {
            head = new Node(data); // 创建第一个结点
        } else {
            Node node = new Node(data); // 创建一个新结点node
            node.next = head; // 将head值赋值给node的next域
            head = node; // 再将node赋值给head
        }
        length++;
        return true;
    }
    // 要插入的结点位置不是1,此时
    // 1、将指针p指向要插入位置的前一个结点上
    // 2、创建新的结点,并插入到p结点后面
    Node p = head; // p指向头结点head,移动到要插入位置的前一个位置
    int i = 1;
    while (i < index - 1 && p != null) {
        p = p.next;
        i++;
    }
    Node node = new Node(data); // 创建该结点
    node.next = p.next;
    p.next = node;
    length++;
    return true;
}

在每次插入成功之后,成员变量length的值会+1,这样每次需要得到链表长度时直接读取length值就可以了。
也可以通过遍历链表的方法获取链表长度,但是效率较低,时间复杂度为 O ( n ) O(n) O(n)
链表是一种动态的数据结构,可以随时在其中插入节点或删除节点,所以链表的长度是不断变化的。我们在插入删除的过程中需要随时修改length值,让length始终记录链表当前的长度,那么爱获取链表长度的时候就不需要重新遍历整个链表了,直接返回length值即可。效率会提高很多,时间复杂度为 O ( 1 ) O(1) O(1)
其中这一段代码需要格外注意,建议多看几遍。

// 1、将指针p指向要插入位置的前一个结点上
// 2、创建新的结点,并插入到p结点后面
Node p = head; // p指向头结点head,移动到要插入位置的前一个位置
int i = 1;
while (i < index - 1 && p != null) {
    p = p.next;
    i++;
}
Node node = new Node(data); // 创建该结点
node.next = p.next;
p.next = node;

删除节点

public boolean deleteNode(int index)
在实现该函数之前,需要明白什么叫链表中第index个位置上的节点删除:

  • 删除链表中index=3的节点就是将链表中的第3个节点移除,使其前驱节点与后继节点直接连接。
  • 删除成功后,链表的长度减1。

显然,index的取值范围为[1,length]
同样,我们需要注意删除第一个节点时的特殊情况

  • 因为index=1的节点前面没有其他节点,也就没有前驱节点,只需要将head.next赋值给head即可。这样head就会指向原链表第一个节点的后继节点,也就等价于删除了第一个节点。
  • 当head指向null时,链表长度为0。将被第一个if拦下,不会执行到这一步,在此不需要考虑这种情况。
public boolean deleteNode(int index) {
    if (index < 1 || index > length) {
        // 这种情况说明插入结点的位置不合法
        System.out.println("Delete error position.");
        return false;
    }
    // 要删除第一个结点
    if (index == 1) {
        head = head.next;
        length--;
        return true;
    }
    // 将p指向index的前一个节点
    Node p = head; // p指向头结点head
    int i = 1;
    while (i < index - 1 && p != null) {
        p = p.next;
        i++;
    }
    p.next = p.next.next;
    length--;
    return true;
}

由于Java自带内存回收机制,所以不需要我们手动释放。如果是C/C++,我们需要手动释放该删除节点在堆区开辟的内存空间。

链表的性能分析

改查慢

之前介绍了数组的性能问题,因为数组存储于连续的内存空间,所以支持随机访问,只要给定数组名和下标,就可以在 O ( 1 ) O(1) O(1)的时间内定位到数组元素。
而链表不支持随机访问,链表的节点是分散存储的,无法通过一个索引在常量时间内定位到链表中的元素,必须从链表头开始顺序遍历链表,所以在链表中定位一个元素的时间复杂度是 O ( n ) O(n) O(n)级别。

增删快

与数组相比,在链表中插入元素和删除元素的效率要高很多,如果已知要插入或删除的节点之前节点的指针,那么插入或删除操作的时间复杂度仅为 O ( 1 ) O(1) O(1)

没有内存越界风险

使用数组时需要预先开辟一整块内存空间,存在内存越界的风险,也可能导致内存资源的浪费。
而链表只需要在使用时动态申请节点,不会产生内存越界,内存的使用效率也相对较高。

综上所述,相较于数组,链表的优势在于能够更加灵活地进行插入和删除操作,且内存使用效率更高。因此对于线性表规模难以估计或插入删除操作频繁、随机读取数据的操作较少的场景,更建议使用链表。

不同形态的链表结构

我们将节点中包含一个指针与且指针只能指向该节点的后继节点的链表称作单链表。
除单链表外,还有功能更强大的循环链表和双向链表。

循环链表

循环链表是一种特殊形式的单链表,它的最后一个节点的指针域不为null,而是指向链表的第1个节点。

普通的单链表只能沿着指针方向找到一个节点的后继节点,无法回到其前驱节点。
由于循环链表的最后一个节点的指针域指向了链表的第一个节点,所以只要通过指针后移,就一定能够找到其前驱节点。

双向链表

单链表的节点只有一个指针域,保存其后继节点的指针。
而双向链表的节点保存了两个指针域,一个指针域的指针指向其直接前驱节点,另一个指针域中的指针指向其直接后继节点。

如果需要经常沿两个方向进行节点操作,那么更适合使用双向链表。

双向循环列表

如果把循环链表和双向链表结合起来,就是结构更为复杂的双向循环链表。

双向循环链表结合了循环链表和双向链表的优点,对节点的操作更加方便灵活。
双向循环链表的结构比其他类型的链表更加复杂,所以还要结合具体选择链表结构。

来几道算法题

链表的综合操作

单链表的增删改查


创建一个包含10个节点的单链表保存整型数据1~10,在屏幕上显示链表中的内容。在链表中第1、3、5、20个位置分别插入一个节点,节点的数据均为0,每插入一个节点,就在屏幕上显示链表中的内容。将插入的节点全部删除,再显示链表中的内容,最后将链表销毁。


对于创建链表,可以通过插入节点的操作来实现。
对于显示链表,可以从链表的第1个节点开始顺序向后遍历整个链表,显示访问到的每个节点。
对于销毁链表,我们不需要调用deleteNode(int index)将链表中的节点逐一删除,这是一种冗余操作。对Java而言,如果一个对象失去了引用,则该对象会被Java的垃圾回收机制回收并释放,因此用户没有必要,也无法显式地释放一个对象实例。所以,要销毁一个链表,只需要将链表的头指针head置为null即可。
如果是C/C++,需要循环调用free()或delete()函数显式地释放内存。

public class MyLinkedList {
	Node head = null;
	int length = 0;

	// main方法,它是程序的入口
	public static void main(String[] args) {
		MyLinkedList list = new MyLinkedList();
		// 通过insertNode方法创建一个链表,里面包含1~10,十个整数结点
		for (int i = 1; i <= 10; i++) {
			list.insertNode(i, i);
		}
		list.printLinkedList(); // 打印链表中的内容
		list.insertNode(0, 1); // 在第1个位置上插入一个包含整数0的结点
		list.printLinkedList(); // 打印链表中的内容
		list.insertNode(0, 3); // 在第3个位置上插入一个包含整数0的结点
		list.printLinkedList(); // 打印链表中的内容
		list.insertNode(0, 5); // 在第5个位置上插入一个包含整数0的结点
		list.printLinkedList(); // 打印链表中的内容

		list.insertNode(0, 20); // 在第20个位置上插入一个包含整数0的结点
		list.printLinkedList(); // 打印链表中的内容

		list.deleteNode(1); // 删除第1个位置上的结点
		list.deleteNode(2); // 删除第2个位置上的结点
		list.deleteNode(3); // 删除第3个位置上的结点
		list.printLinkedList(); // 打印链表中的内容

		list.destroyLinkedList(); // 销毁链表
	}

	public boolean insertNode(int data, int index) {
		// 向链表的第index位置上插入一个结点,结点数据为data(这里是int类型)

		if (index < 1 || index > (length + 1)) {
			// 这种情况说明插入结点的位置不合法
			System.out.println("Insert error position. index = " + index + " length = " + length);
			return false;
		}
		// 要插入结点的位置为1,这时的操作有些特殊
		if (index == 1) {
			if (head == null) {
				head = new Node(data); // 创建第一个结点
			} else {
				Node node = new Node(data); // 创建一个新结点node
				node.next = head; // 将head值赋值给node的next域
				head = node; // 再将node赋值给head

			}
			length++;
			return true;
		}

		// 要插入的结点位置不是1,此时
		// 1、将指针p指向要插入位置的前一个结点上
		// 2、创建新的结点,并插入到p结点后面

		Node p = head; // p指向头结点head
		int i = 1;
		while (i < index - 1 && p != null) {
			p = p.next;
			i++;
		}

		Node node = new Node(data); // 创建该结点
		node.next = p.next;
		p.next = node;
		length++;
		return true;
	}

	public boolean deleteNode(int index) {

		if (index < 1 || index > length) {
			// 这种情况说明插入结点的位置不合法
			System.out.println("Delete error position.");
			return false;
		}
		// 要删除第一个结点
		if (index == 1) {
			head = head.next;
			length--;
			return true;
		}
		// 将p指向index的前一个节点
		Node p = head;
		for (int i = 1; i < index - 1; i++) {
			p = p.next;
		}
		p.next = p.next.next;
		length--;
		return true;
	}

	public void printLinkedList() {
		Node p = head;
		while (p != null) {
			System.out.print(p.data + " ");
			p = p.next;
		}
		System.out.print("\n-------------------------\n"); // 打印分隔线
	}

	public void destroyLinkedList() {
		head = null; // 头结点指针head置null
		length = 0; // 链表长度length设为0
	}
}

class Node {
	int data;
	Node next;

	public Node(int data) {
		this.data = data; // 构造方法,在构造结点对象时将data赋值给this .data成员
	}
}

将两个有序链表归并


编写一个函数MyLinkedList MergeLinkedList(MyLinkedList list1,MyLinkedList list2),实现将有序链表list1和list2合并成一个链表。要求合并后的链表依然按值有序,且不开辟额外的内存空间。


本体要求不开辟额外的内存空间,也就是要利用原链表的内存空间,在不创建新节点的前提下实现链表的合并。
一共需要创建四个Node类型的引用变量:

  • head3:作为结果链表list3的头指针。由于不开辟额外的内存空间,所以需要指向head1head2节点中的较小者,使用该链表的内存空间。
  • r:指向list3的最后一个节点。使用该变量是为了方便在list3的链表尾插入新的节点。
  • p、q:分别指向list1list2中待合并的节点。将对比得到的较小值插入到r后面。

由于我们head3初始指向了head1head2节点中的较小者。只确定了链表的第一个节点,所以此时list3长度为1,rhead3指向的是同一个对象。
在p、q的对比过程中,得到的较小值插入到了r后面。较大值并没有被插入,需要继续拿来对比。

上图中,q小于p,所以:

  1. q赋值给r.next。使3接入到链表尾部。
  2. r.next赋值给r。使r指向新的尾节点。
  3. q.next赋值给q。使q指向原链表的下一个节点。

当p或q等于null时结束循环。此时list1或list2至少有1个链表的节点已经全部合并到list3中。将尚未合并到list3中的链表整体插入到r指向的节点后面实现完整的合并操作。

public class MergeLinkedListTest {
//将以链表list1和链表list2合并,返回链表list3
public static MyLinkedList MergeLinkedList(MyLinkedList list1, MyLinkedList list2) {
    Node head3;   //定义head3,指向链表list3的头结点
    Node p = list1.getHead();//通过getHead()方法获取list1的头结点,并用p指向list1的头结点
    Node q = list2.getHead();//通过getHead()方法获取list2的头结点,并用q指向list1的头结点
    Node r;  //定义r指针
    if (p.data <= q.data) {
        //如果p结点的数据小于等于q结点数据
        head3 = p;  //head3指向p结点(list1的头结点)
        r = p;    //r指向p结点
        p = p.next;    //p指向下一个结点
    } else {
        //如果p结点的数据大于q结点数据
        head3 = q;  //head3指向q结点(list2的头结点)
        r = q;    //r指向q结点
        q = q.next;    //q指向下一个结点
    }

    while (p != null && q != null) {
        //进入循环,直到p或q等于null,也就是一个链表遍历结束
        if (p.data <= q.data) {
            //如果p结点的数据小于等于q结点的数据
            //则将p结点插入到r结点后面
            r.next = p;
            r = r.next;
            p = p.next;
        } else {
            //如果q结点的数据小于p结点的数据
            //则将q结点插入到r结点后面
            r.next = q;
            r = r.next;
            q = q.next;
        }
    }
    r.next = (p != null) ? p : q; //将p或q指向的剩余链表连接到r结点后面
    MyLinkedList list3 = new MyLinkedList(); //创建list3实例
    list3.setHead(head3);    //将head3赋值给list3中的head成员
    return list3;        //返回list3实例引用
}

因为MergeLinkedList()函数的参数是MyLinkedList类型对象引用,MyLinkedList对象包含了链表的头节点指针head,所以我们需要获取这个head指针才能对链表进行操作。
整个合并过程中没有开辟额外的内存空间,而是利用原链表的节点资源,通过调整指针实现链表的合并,符合题目要求。

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

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

相关文章

亲测好用|甲方、专家和领导,用三维模型汇报方案如何投其所好?

身为设计方的你&#xff0c;有没有这样的经历&#xff1a; ➤ 一个非常优秀的方案未能被甲方采纳&#xff0c;反而甲方选择了一个不如自己的方案&#xff0c;造成了很大的遗憾&#xff1b; ➤ 在讲述自己的设计方案的时候&#xff0c;经常越说越散&#xff0c;甚至到了最后自…

ACM - 搜索与图论 - 基础(搜索 + 拓扑 + 最小生成树 + 最短路 + 二分图 + 欧拉路 + 最大流 + 其它)

搜索与图论 一、搜索1、分治矩阵二分 / 普通二分 模板万能二分模板 2、DFS例题1、AcWing 842. 排列数字例题2、AcWing 843. n-皇后问题 3、BFS例题1、AcWing 844. 走迷宫 (入门经典)例题2、到达 "12345678x"&#xff1a;AcWing 845. 八数码 二、图论写在前面1、图的存…

自学软件测试,从10K到40K的技术路线,也就是这些东西...

如果有一天我从梦中醒来时&#xff0c;发现自己的几年自动化测试工程师经验被抹掉&#xff0c;重新回到了一个小白的状态。我想要重新自学自动化测试&#xff0c;然后找到一份自己满意的测试工作&#xff0c;我想大概只需要6个月的时间就够了&#xff0c;如果比较顺利的话&…

Cursor--一款强大的国内智能AI语言模型(据说对接了chatgpt4)

简介 Cursor是一个集成了 GPT-4 的国内直接可以访问的&#xff0c;优秀而强大的免费代码生成器&#xff0c;可以帮助你快速编写、编辑和讨论代码。 它支持多种编程语言&#xff0c;如 Python, Java, C#, JavaScript 等&#xff0c;并且可以根据你的输入和需求自动生成代码片段…

eSIM证书要求-涉及规范SGP.22-SGP.26-1

文档 Official Document SGP.22 – RSP Technical Specification v2.4 Official Document SGP.26 - RSP Test Certificates Definition &#xff0c;包含官方测试证书 证书链的定义 三个曲线 为了便于互操作性&#xff0c;本规范仅限于以下三个曲线 AlgorithmIdentifier算法标…

英飞凌的AURIX TC4x系列芯片在汽车控制器上应用

英飞凌的AURIX TC4x系列微控制器--智能汽车安全的领导者来了! 汽车市场上出现了重大的颠覆者&#xff1a; 汽车E/E架构微控制器&#xff08;MCU&#xff09;在领域和区域控制方面的创新 加速从传统动力系统到xEV的转变 安全性和自动驾驶的提高。 英飞凌的AURIX TC4x系列解…

yolov5 8系列 labelme数据标注 并生成训练数据集

yolov5 8系列 labelme数据标注 数据集生成终极教程 一.数据集准备二.转换为yolo 数据集 一.数据集准备 创建一个data 文件夹在data文件夹下创建一个images 文件夹将所有图片数据放入images文件夹下 使用labelme标注数据 python环境下使用 pip install labelme 安装labelme在c…

边缘计算盒子要怎么选?

选择边缘计算盒子需要考虑以下几个因素&#xff1a; 性能需求&#xff1a;边缘计算盒子的性能应该与您的应用需求相匹配。如果需要处理大量数据或者部署多种复杂ai算法&#xff0c;那么就需要选择更高性能的盒子。 IVP06A边缘计算盒子是基于RV1126设计的一款支持深度智能边缘…

Makefile基础教程(条件判断语句)

文章目录 前言一、条件判断语句概念讲解1.ifeq 和 ifneq2.ifdef 和 ifndef3.ifeq 的比较操作符 二、条件判断语句的使用三、条件判断语句使用的注意事项四、条件判断语句只在预处理阶段有效总结 前言 本篇文章开始讲解Makefile中的条件判断语句&#xff0c;在各种编程语言中都…

远程桌面连接可以传文件么?

远程桌面连接是一种远程管理计算机的方式。它允许用户通过网络远程控制其他计算机。远程桌面连接可以用于各种目的&#xff0c;例如从远程地方访问办公室电脑、支持远程用户、教育、游戏等等。但是&#xff0c;在使用远程桌面连接时&#xff0c;用户可能会遇到一些问题&#xf…

8. 类的静态成员

一、对象的生产期 生存期&#xff1a;对象从诞生到结束的这段时间生存期分为静态生存期和动态生存期 1.1 静态生存期 对象的生存期与程序的运行期相同&#xff0c;则称它具有静态生存期在文件作用域中声明的对象都是具有静态生存期的若在函数内部的局部作用域中声明具有静态…

配置Windows终端像Linux终端一样直接执行Python脚本

配置Windows终端像Linux终端一样直接执行Python脚本 1. 将Python加入环境变量2. 将Python后缀加入环境变量PATHEXT中3. 修改Python脚本的默认打开方式4. *将Python脚本命令加入环境变量*5. 测试 在Linux系统中&#xff0c;在Python脚本的开头指定Python解释器路径&#xff0c;即…

Terry部落简介

通过一个简单的服务&#xff0c;整理使用的技术 服务简介 Terry部落&#xff0c;基于目前主流 Java Web 技术栈&#xff08;SpringBoot MyBatis-plus MySQL Redis Kafka Elasticsearch shiro …&#xff09;实现的个人博客服务。包含登陆、注册、文章发布、资源发布、鉴…

ACM - 数学 - 提高(还没学多少)

ACM - 数学 练习题 一、数论1、分解质因数 &#xff1a;AcWing 197. 阶乘分解2、求约数个数&#xff08;1&#xff09;AcWing 1294. 樱花 &#xff08;求 n&#xff01;约数个数之和&#xff09;&#xff08;2&#xff09;AcWing 198. 反素数 &#xff08;求 1 ~ N 中约数最多的…

human-NeRF 代码运行环境完全打包(根据照片视频生成三维模型)

包含模型和可以直接运行的数据的代码环境&#xff08;window环境&#xff0c;linux应该也可以运行&#xff09;&#xff0c;下载链接放在文章最后&#xff0c;不需要你自己再去下载任何的代码和模型了。 下载后输入三行命令在命令行界面即可将代码跑起来&#xff1a; pip insta…

C++14:AVL树

由于二叉搜索树在某些特定的情况下会退化成单叉树,为了解决这个问题&#xff0c;保证二叉搜索树能在绝大多数情况下保持高速搜索&#xff0c;G.M. Adelson-Velsky和E.M. Landis这两位俄国数学家提出了AVL树的概念&#xff0c;也就是高度平衡的搜索二叉树。 AVL树平衡大体逻辑&…

ACM - 数据结构 - 基础(数组模拟链表 / 栈 / 队列 + 字典树 + 并查集 + 堆 + 哈希)

数据结构 一、线性表1、单链表模板题&#xff1a;AcWing 826. 单链表 2、双链表模板题 AcWing 827. 双链表 3、栈数组模拟栈模板 AcWing 828. 模拟栈逆波兰简版模板例题1、逆波兰表达式&#xff1a;HDU 1237 简单计算器&#xff08;写得有点复杂&#xff09; 4、队列数组模拟队…

接口自动化测试可以使用的几个常用的框架

接口自动化测试可以使用以下几个常用的框架&#xff1a; 1、pytest pytest是一个用于Python编写单元测试和功能测试的框架。它提供了简洁的语法、灵活的扩展性和丰富的插件&#xff0c;可以帮助开发人员高效地编写测试用例&#xff0c;并快速定位和解决问题。 以下是pytest的…

数据结构刷题(三十一):1049. 最后一块石头的重量 II、完全背包理论、518零钱兑换II

一、1049. 最后一块石头的重量 II 1.思路&#xff1a;01背包问题&#xff0c;其中dp[j]表示容量为j的背包&#xff0c;最多可以背最大重量为dp[j]。 2.注意&#xff1a;递推公式dp[j] max(dp[j], dp[j - stones[i]] stones[i]);本题中的重量就是价值&#xff0c;所以第二个…

边缘计算盒子适合用于哪些场景?

边缘计算盒子适用于在智慧工地、智慧工厂、智慧园区和智慧城管等场景中可以实现多种算法功能&#xff0c;以下是一些应用和实现算法功能&#xff1a; 智慧工地&#xff1a;实时视频监控与分析&#xff1a;边缘计算盒子可以处理实时监控视频流&#xff0c;进行人员和车辆识别、…