java数据结构-链表经典习题

前言

上一篇讲解了链表的基本操作详解,接下来练习一下链表的应用。

目录

1.删除链表中等于给定值 val 的所有节点。

 题解思路

 2.反转一个单链表

思路分析

画图分析 

 代码实现

3.链表的中间结点

思路分析

画图分析

 代码实现

 4.链表中倒数最后k个结点

第一解决办法

第二种解决办法

5.合并两个有序链表

思路分析

画图分析

 代码实现

6. 链表分割

思路分析

画图分析

 代码实现

 7.链表回文

思路分析

画图分析

代码实现

8. 相交链表

思路分析

画图分析

代码实现

9.环形链表1

思路分析

画图分析

代码实现

10.环形链表2

思路分析

画图分析

代码实现

总结


1.删除链表中等于给定值 val 的所有节点。

移除链表元素习题链接: 移除链表元素

 题解思路

实现在链表中删除所有给定的key

1.首先先遍历链表

2.如果有某个节点的val值等于vag,那么让这个节点的上一个节点next存放该节点的next(相当于就是跳过了那个相等的节点,跳过了相当于就是删除了)

3.然后继续遍历找下一个相同的,找到进行同样的操作。

4.最后解决头部问题,对链表的头的val与val进行比较,如果同则让头指向下一个节点即可。

 

代码实现

public ListNode removeElements(ListNode head, int val) {
        //看看是不是空的
        if(head == null){
            return null;
        }
        
        //定义两个节点指针,指向头另一个指向头的下一个
        ListNode pevr = head;
        ListNode cur = head.next;
        
        while(cur != null){
            //2.如果有某个节点的val值等于vag,那么让这个节点的上一个节点next存放该节点的next
            // (相当于就是跳过了那个相等的节点,跳过了相当于就是删除了)
            if(cur.val == val){
                pevr.next = cur.next;
                cur = cur.next;
               

            }else{
                pevr = cur;
                cur = cur.next;
            }
        }
         //最后解决头部问题,对链表的头的val与val进行比较,如果同则让头指向下一个节点即可。
        if(head.val == val){
            head = head.next;
        }
        return head;

    }

 2.反转一个单链表

反转链表链接:反转链表

思路分析

反转一个链表是需要改变其链表结构的,

翻转链表,就是让第二个节点开始,依次进行头插

要做到这一步,需要设置两个指针,一个指向头节点的下一个节点

然后另一个指针就是保留这个节点的下一个节点,让第一个指针节点进行头插后,

再回来还能找到下一个要插入的节点地址,然后头结点变成插入好的节点,就这样遍历一下链表即可反转完成。

步骤

1.遍历链表

2.进行头插,改变头结点

3.继续下一个节点的头插,重复遍历完即可。

画图分析 

 反转前

反转后

 代码实现

 //翻转链表,就是让第二个节点开始,依次进行头插
    public  ListNode overturn(){
        if (head ==null){
            return null;
        }

        //如果是一个结点直接
        if (head.next ==null){
            return head;
        }
        //改变链表内部结构
        ListNode cur = head.next;

        head.next = null;
        while(cur != null){
            //1.循环条件咋么设置,还是cur != null,因为最后一个是null
            // 头插能实现第一个,但是后面的2接上呢,让cur返回来就行了
            ListNode curNode = cur.next;
             cur.next = head;
             head = cur;
             cur = curNode;//当做指针
        }
        return head;
    }

分析

              ListNode curNode = cur.next;
             cur.next = head;
             head = cur;
             cur = curNode;//当做指针

3.链表的中间结点

给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结 点。

习题链接:链表中间结点

思路分析

利用两个指针,一个是慢一个快,快的是一次走两步

原理就是路程相同为800米的田径场跑道,A同学和B同学同时起跑,A同学是B同学的速度的2倍,那么当A到达终点,B肯定在中点。

1.创建两个指针同时指向头结点;

2.然后遍历一遍,注意循环结束的条件是判断快指针是否到达终点;

3.走完一遍,返回慢指针即是中间结点。

画图分析

第一步

第二步

第三步

此时当fast到达末尾,那么slow必然在中间位置。

 代码实现

//快慢,速度为2倍,同样的起点,终点一样,速度是2倍
    public ListNode middleNode(ListNode head){
        ListNode fast = head;
        ListNode slow = head;
        while( fast  != null && fast.next !=null  )//fast两倍速度会提前一半到终点
        //奇数情况fast.next ==null,偶数情况fast  == null
        //这里需要注意,这两个条件不能换过来。
            {
                
                fast = fast.next.next;//2倍速度走两步
                slow = slow.next;//慢的走一步

        }
        //走完说明slow在中间
        return slow;

    }

 4.链表中倒数最后k个结点

习题链接:链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com)

题目描述:输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

如果该链表长度小于k,请返回一个长度为 0 的链表。

第一解决办法

比如要返回倒数第2个结点,那么直接用链表的 总长度 - 2,得到的数就是头结点要走的长度。

走完,返回fast即可。

代码实现


        ListNode fast = head;
        int count = 0;
        while(fast != null){
            count++;
            fast = fast.next;
        }
        //限制一下k范围
        if(head == null || k < 0 || k >count){
            return null;
        }
        fast = head; //重置为头位置
        int len = count - k; //
        while(len > 0){
            fast = fast.next;
            len--; 

        }
        return fast;

  

第二种解决办法

思路分析

输入一个链表,输出该链表中倒数第k个结点。
1.fast 先走k-1步
2.然后slow和fast同时走
3.fast到最后slow的位置就是倒数k-1
需要注意的是要对k进行判断是否合法
还需要解决当链表为空或者fast指向了空的情况

原理fast和slow永远相差k-1步

画图分析

 代码实现

        //限制k值和防止空表
        if ( k <= 0 || head == null ) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;

        //怎么先让fast先走k-1步
        for (int i = 0 ; i < k - 1 ; i ++) {

            fast = fast.next;//如果k>size()那么fast就指向null了
            if (fast == null) { //处理k太大问题
                return null;
            }
        }
        //同时走
        //奇偶情况是如何的呢,通过画图分析是一样的
        while (fast.next != null) {

            fast = fast.next;
            slow = slow.next;

        }
        //走完了然后勒
        //走完就说明slow就是要求的位置
        System.out.println(slow.val);
        return slow;
    

5.合并两个有序链表

习题链接:21. 合并两个有序链表 - 力扣(LeetCode)

题目描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

思路分析

合并两个链表,要改变其结构

1.创建好一个新的链表的头结点,相当于一辆火车先有车头

2.然后让两个链表的头结点分别进行比较大小,小的先在新的链表里面进行尾插,然后头结点更换到下一个,然后继续比较大小。

3.最后比较完成,拼接完成新链表,返回新链表的头结点即可。

画图分析

 代码实现

 public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
        //1.定义虚拟的字节,作为新链表头结点
       ListNode newH = new  ListNode(-1);
       ListNode tmpH = newH;
        //2.遍历进行比较大小
        while(head1 != null && head2 != null){
            if(head1.val < head2.val){
                
                tmpH.next = head1; //进行尾插到新的链表中
                head1 = head1.next; //头结点改变下一个。
                
            }else{
                
                tmpH.next = head2; 
                head2 = head2.next; 
                

            }
            tmpH = tmpH.next;//指向末尾
        }
        //可能两个链表长度不一样,出了循环进行检查
        if(head1 != null){
            tmpH.next = head1;
        }
        if(head2 != null){
            tmpH.next = head2;
        }
        //3.返回新链表头结点
        
    return newH.next;
    }

6. 链表分割

习题链接:链表分割_牛客题霸_牛客网 (nowcoder.com)

题目描述:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

思路分析

链表的分割,要分割的话就要分两个区间,一个存放小于x的区间,另一个则是大于x的。

1.那么就有先创建四个指针分别为第一第二区间链表的头尾指针。

2.然后对链表进行一次遍历,每个结点的值依次与x比较,然后放到对应的区间。

3.遍历完,然后进行连接成一个新的链表,再返回新链表的头结点。

需要注意的是两种情况,如果第二区间是空的则无需连接

还有就是如果第一个区间为空,那么直接返回第二区间的头结点

最后就是要手动给第二区间的最后一个结点的next置为null不然会报错异常。

画图分析

第一步,第一个结点12小于35,因此放在第一个区间be、bs这时都指向第一个结点

第二步,第二个结点值是5也小于35,放第一个区间,然后尾指针be指向他。

第三步,第三个结点值大于35,放到第二区间,后续遍历也如此,这里就不一一列出。

最后结果

 代码实现

 //会改变链表的结构
    public ListNode partition(ListNode head, int x) {
        // write code here
        
        ListNode cur = head;

        ListNode bs = null;//创建好节点类型的用于保存分割后两个区间的节点位置地址
        ListNode be = null;

        ListNode as = null;
        ListNode ae = null;

        while(cur != null){//遍历
            //分割
            if(cur.val < x ){
                if(bs == null){//如果是第一个头尾都指向他
                    bs = cur;
                    be = cur;

                }else{
                    //如果是第二个以后进行尾插
                    be.next = cur;//尾插原理
                    be = be.next;
                }

            }else{
                if(as == null){
                    as = cur;
                    ae = cur;
                }else{
                    ae.next = cur;
                    ae = ae.next;
                }

            }
           cur = cur.next;
        }
        //有一种情况,如果链表是3333,x为3,那么前面区间是空的
        //此时be.next会空指针异常
        //因此要对第一区间进行判断
        if(be == null){
            return as;//如果第一区间是空那么没必要连接,返回第二区间的头即可。
        }

        //连接起来
        //用第一个区间的尾和第二个区间的头进行连接
        be.next = as;

        //还有一个情况,就是拼接起来后,第二区间的尾节点的next有可能不为空,异常
         if(as != null ){//此时需要手动进行置空
            //第二区间没有数据情况
            ae.next = null;
        }
       return bs;//返回第一区间的头

    }

 7.链表回文

习题链接:链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

题目描述:

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

思路分析

要判断是否是回文,那么就对前后两边进行比较,如果相同两边同时走一步,如果不是同则返回false,然后继续判断,直到结束,能走到结束说明是回文结构。

那么要实现上面的效果,首先就得需要改变链表后半部分的结构。

1.首先要用快慢指针找到中间位置,然后翻转中间后面节点(使用上面的翻转思路)。

2.翻转完成,此时slow处于链表末尾,让头head和尾slow同时往中间走,每走一步进行比较val内容是否相同,然后继续下一步,直到循环结束。

3.走完返回true。

需要注意的是需要区分奇数个节点和偶数个节点的区别就行。

画图分析

1.快慢指针取中间

2.翻转后面节点

3.前后往中走

4.解决偶数问题

代码实现

    public boolean chkPalindrome(ListNode head) {     
    if(head == null || head.next == null){
            return true;
        }
        // write code here
        //中间位置
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null ){//这个顺序不能改啊,因为会出现空指针异常
            fast = fast.next.next;//走两步
            slow = slow.next;
        }
        //走完即可得到中间位置

        //从中间开始翻转
        //定义两个指向slow的后面,把slow当做头,然后进行翻转
        ListNode cur = slow.next;
        

        while(cur != null){
            ListNode  curNext = cur.next;//提前存好cur的下一个地址,不然丢失
            cur.next = slow;//slow后面指向存放slow地址
            slow = cur; //然后slow相当于头结点往后走
            cur = curNext;//然后继续下一个节点翻转

        }

        //走到这里翻转完成,此时slow指向最后一个节点位置
        //3.进行前后移动
        //fast = head; //让fast回到开头
        //此时fast和slow一个在头一个在尾,进行移动即可。
        //可是怎么知道走到中间了呢
        while(head != slow){ 
            //因为后面被翻转了,所以中间的左右节点的next放的地址都是中间的地址。
            if(head.val != slow.val){//进行值的对比,如果有一个不对就不是回文
                return false;

            }
            //解决偶数的情况
            if(head.next == slow){
                return true;
            }
            //这三步骤得按顺序来
           head = head.next;
            slow = slow.next;
                
        }
        return true;
        
    }

8. 相交链表

题目链接:160. 相交链表 - 力扣(LeetCode)

题目描述:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

思路分析

    1.分别求长度

    2.求两个长度差值,让长的先走完差值步数

    3.然后同时走

    4.当他们相遇的时候那个相遇点就是相交节点

画图分析

代码实现

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 忽略了一点,这俩链表不一定都是在第n个节点相交,所以要让长度长的提前走几步
        int len1 = getLen(headA);
        int len2 = getLen(headB);

        // 俩指针分别指向俩链表,如果某一个指针到达尾部还没有匹配到相交点,则无交点
        ListNode fast = headA;
        ListNode slow = headB;
        
         //让长的走差值步
        if (len1 > len2) {
            for (int i = 0; i < len1 - len2; i++) {
                fast = fast.next;
            }
        } else if (len2 > len1) {
            for (int i = 0; i < len2 - len1; i++) {
                slow = slow.next;
            }
        }

        //“同步进度之后同时走再找交点”
        while (fast != null && slow != null) {
            if (fast == slow)
                return fast;
            fast = fast.next;
           slow = slow.next;
        }
        return null;
    }

    int getLen(ListNode head) {
        ListNode cur = new ListNode();
        cur.next = head;
        int res = 0;
        while (cur.next != null) {
            res++;
            cur = cur.next;
        }
        return res;
    }

9.环形链表1

题目链接:141. 环形链表 - 力扣(LeetCode)

题目描述:

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

思路分析

原理可以理解为追击问题

一个田径场圈,两人速度相差一倍,一定会有相遇的时候,相遇就是有环。

1.利用快慢指针,一个走两步,一个走一步

2.如果有环,那么两者肯定会相遇,每走完一步就进行判断

画图分析

代码实现

public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;

        while(fast != null && fast.next != null){
            fast = fast.next.next; //走两步
            slow = slow.next;
            if(fast == slow){//每走完一步就进行判断
                return true;
            }
        }
        return false;
        
    }

10.环形链表2

题目链接:142. 环形链表 II - 力扣(LeetCode)

题目描述:

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL

不允许修改 链表。

思路分析

找入环点。

让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针 都是每次均走一步,最终肯定会在入口点的位置相遇。

画图分析

代码实现

public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;

        while(fast != null && fast.next != null){
            fast = fast.next.next; //走两步
            slow = slow.next;
            if(fast == slow){//每走完一步就进行判断
                break;
            }
        }
        if(fast ==null || fast.next == null){
            return null;
        }

        //直接让慢指针从头开始走
        //让fast在相遇点那里走,再次相遇就是入口点。
        slow = head;
        while(slow != fast){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

总结

本篇主要对链表的习题进行讲解,如果有疑惑的或者讲解的地方有问题,可以在评论区交流谢谢。

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

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

相关文章

RS-485和RS-422通信的3.3V低功耗收发器MAX3483

描述 多数公司的MAX3483速率为&#xff1a;250kbps&#xff1b; Analog Devices公司的MAX3483速率为10Mbps。 国产MAX3485外观和丝印 该MAX3483ESA为15kV ESD保护、3.3V、低功耗收发器&#xff0c;用于RS-485和RS-422通信。 每个设备包含一个驱动器和一个接收器。 该MAX3483E…

Open3D通过索引提取点云

目录 一、概述 二、代码实现 2.1关键函数 2.2 完整代码 三、实现效果 3.1原始点云 3.2提取后点云 一、概述 在 Open3D 中&#xff0c;通过索引提取点云是一种常见且有效的操作&#xff0c;特别适用于需要处理点云子集的场景&#xff0c;例如提取特定区域的点、降采样、或…

CPsyCoun:心理咨询多轮对话自动构建及评估方法

CPsyCoun: A Report-based Multi-turn Dialogue Reconstruction and Evaluation Framework for Chinese Psychological Counseling 在大模型应用于心理咨询领域&#xff0c;目前开源的项目有&#xff1a; https://github.com/SmartFlowAI/EmoLLM &#xff08;集合&#xff0c;…

query2doc:用大模型做query检索拓展

原文&#xff1a; 前沿重器[38] | 微软新文query2doc&#xff1a;用大模型做query检索拓展 比较主流的检索方案&#xff1a; 字面检索&#xff08;sparse&#xff0c;稀疏&#xff09;向量检索&#xff08;dense&#xff0c;稠密&#xff09; query对文档文段的召回&#xff…

如何在Ubuntu上安装WordPress

如何在Ubuntu上安装WordPress 执行系统更新 apt update && apt upgrade第一步 安装 Apache apt install apache2确认 Apache 安装是否成功. systemctl status apache2安装成功后 打开浏览器输入 http://server-ip-address 第二步 安装 MySQL apt install mariad…

大模型揭秘:AI与CatGPT在实体识别中的创新应用

摘要 尽管大规模语言模型 (LLM) 在各种 NLP 任务上已经取得了 SOTA 性能&#xff0c;但它在 NER 上的性能仍然明显低于监督基线。这是由于 NER 和 LLMs 这两个任务之间的差距&#xff1a;前者本质上是序列标记任务&#xff0c;而后者是文本生成模型。在本文中&#xff0c;我们…

劳易测应用案例:包装机械设备风险评估

提起机器风险评估&#xff0c;客户经常会问 “机器存在哪些风险&#xff1f;”、“如何识别并防止风险&#xff1f;”、“如何依据安全标准对机器进行改造与升级&#xff1f;”以及“如何确保机器符合安全要求&#xff1f;等等。 机器风险评估是什么&#xff1f; 机器风险评估是…

鸿蒙HarmonyOS服务卡片实战

引言 在现代开发中&#xff0c;服务卡片是不可或缺的一部分&#xff0c;比如音乐&#xff0c;天气类等应用&#xff0c;官网的介绍中写道&#xff1a;卡片让您便捷地预览服务信息&#xff0c;例如查看天气或日历日程等内容。您可将卡片添加到屏幕上&#xff0c;让这类信息触手…

【大数据】Hadoop学习笔记

基本概念 Hadoop组成 HDFS: Hadoop分布式文件存储系统, 在Haddop中处于底层/核心地位YARN: 分布式通用的集群资源管理系统和任务调度平台, 支撑各种计算引擎执行MapReduce: 第一代分布式计算引擎, 但因为部分原因, 许多企业都不直接使用MapReduce, 但许多底层软件仍然在使用Ma…

SQL Server - ROLLUP、GROUPING、CUBE、GROUPING SET

文章目录 SQL Server - ROLLUP、GROUPING、CUBE、GROUPING SETROLLUP函数GROUPING函数GROUPING SET函数CUBE函数网上例子 写在前面&#xff1a;如果我们想要对分组之后的数据进行类似小计的计算&#xff0c;那么就需要使用到下面的函数 SQL Server - ROLLUP、GROUPING、CUBE、G…

PR模板 | RGB特效视频标题模板Titles | MOGRT

RGB特效视频标题模板mogrt免费下载 4K分辨率&#xff08;38402160&#xff09; 支持任何语言 友好的界面 输入和输出动画 快速渲染 视频教程 免费下载&#xff1a;https://prmuban.com/39055.html 更多pr模板视频素材下载地址&#xff1a;https://prmuban.com

【网络协议】精讲TCP通信原理!图解超赞超详细!!!

亲爱的用户&#xff0c;打开微信&#xff0c;搜索公众号&#xff1a;“风云说通信”&#xff0c;即可免费阅读该文章~~ 目录 1. 建立连接 2. 数据传输 3. 断开连接 4. 抓包分析 前言 TCP 把连接作为最基本的对象&#xff0c;每一条 TCP 连接都有两个端点&#xff0c;这种端…

【Day02】0基础微信小程序入门-学习笔记

文章目录 模板与配置学习目标WXML 模板语法1.数据绑定&#xff08;类似于 Vue2 &#xff09;2. 事件绑定3. 条件渲染4.列表渲染 WXSS模板样式1. rpx尺寸单位2.样式导入3. 全局样式和局部样式 全局配置1. window2. tabBar 页面配置网络数据请求总结 持续更新~ 模板与配置 学习目…

《第一行代码 第3版》学习笔记——第十一章 网络技术

1 webview用法 class MainActivity : ComponentActivity() {SuppressLint("SetJavaScriptEnabled")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {NetWorkDemoTheme {// A surface container using the bac…

GandCrab5.2勒索病毒复现

GandCrab第一代勒索病毒首次出现于2018年1月&#xff0c;后面经历了五个大版本的更新迭代&#xff0c;该系列病毒特征是采用RSAAES加密算法&#xff0c;从算法上分析解密难度较大&#xff0c;会将系统中的大部分文件加密为随机后缀名的文件&#xff0c;然后对用户进行勒索。本实…

国内邮件推送如何避免拦截?内容优化技巧?

国内邮件推送的平台怎么选择&#xff1f;如何提高邮件推送效果&#xff1f; 邮件营销是企业与客户沟通的重要方式&#xff0c;但在国内邮件推送过程中&#xff0c;邮件被拦截的问题屡见不鲜。为了确保邮件能够顺利送达目标用户&#xff0c;AokSend将探讨一些有效的策略&#x…

【亲测好用】神级PSAI插件大揭秘:三款创成式修图神器,让你解放双手

PsBeta被停用后&#xff0c;小编一直想找到能够平替PsBeta创成式填充功能的插件。 功夫不负有心&#xff0c;终于被我找到啦&#xff0c;现在就给大家揭秘这三款宝藏修图神器&#xff0c;希望能够帮到大家。 1.插件名称&#xff1a;Starai 无需科学上网&#xff0c;还自带提示…

wireshark常用过滤命令

wireshark常用过滤命令 wireshark抓包介绍单机单点&#xff1a;单机多点&#xff1a;双机并行&#xff1a; wireshark界面认识默认布局调整布局(常用)显示FCS错误 wireshark常见列Time回包数据报对应网络模型 wireshark基本操作结束抓包再次开始抓包 **wireshark常用过滤命令**…

乐鑫云方案研讨会回顾|ESP RainMaker® 引领创业潮,赋能科创企业

近日&#xff0c;乐鑫信息科技 (688018.SH) ESP RainMaker 云生态方案线下研讨会和技术沙龙在深圳成功举办&#xff0c;吸引了众多来自照明电工、新能源、安防、宠物等垂类领域的客户与合作伙伴。活动现场&#xff0c;与会嘉宾围绕产品研发、测试认证、品牌构建、跨境电商等多维…

揭秘循环购:消费即收益,如何助力商家月销百万?

大家好&#xff0c;我是吴军&#xff0c;今天要和大家分享一种颠覆性的商业模式——循环购。你是否听说过“消费1000送2000”这样的促销活动&#xff1f;是不是觉得太不可思议&#xff0c;商家岂不是在“送钱”&#xff1f;别急&#xff0c;让我为你揭开这背后的秘密。 循环购&…