动态规划——子序列、编辑距离、回文子串

news2024/10/3 4:38:24

目录

子序列问题

300.最长递增子序列

674.最长连续递增序列

718.最长重复子数组

1143.最长公共子序列

1035.不相交的线

53.最大子序和

编辑距离

392.判断子序列

115.不同的子序列

583.两个字符串的删除操作

72.编辑距离

回文子串

647.回文子串

516.最长回文子序列

 698.划分k个相等的子集


子序列问题

本文讲解力扣上所有典型的子序列、编辑距离、回文子串的题目,使用动态规划的解法来做。

300.最长递增子序列

首先我们应该自然而然的想到,题目要我们求的是整个长序列的最长上升子序列的长度,那么也就是说不论多长的一个序列,我们都可以求出这个序列的长度,(也就是子问题)

而事实上,整个序列的值又与其部分子序列是息息相关的。

就例如:比如对于7,它的最长子序列可以来自3,也可以来自5,也可以来自2,

那这三个选择哪个呢?很好想,因为以7结尾的子序列的长度,就等于2、5、3结尾的子序列长度+1即可。

那么我们只要对比以2、5、3作为最后字母的子序列,谁长就行了。

那自然而然就能明白,其实遍历顺序就是,i在外层,当i为2时,j从0、1、2,开始,

对比nums[i]和num[j],假如nums[i]>nums[j],则说明i可以和j的子序列拼接起来

 那么接下来只需要对比i前面的子序列,谁的最大子长度最长即可。

而像这种,求一个整体的数值的问题,可以归结于求其子部分,而子部分又是由前面的子部分所决定,并且还是数组型的,那么就代表着这可以用动态规划!

 

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int len=nums.size();
        vector<int> dp(len,1);//最长的子序列长度至少是自身,故至少为1

        //int maxLen=0;//错,长度不可能为0
        int maxLen=1;//长度至少也为1
        for(int i=1;i<len;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j])dp[i]=max(dp[i],dp[j]+1);
                
            }
            maxLen=max(maxLen,dp[i]);
        }

        return maxLen;
    }
};

注意特殊案例:

 可能有时候就是卡在一些特殊样例上过不去

674.最长连续递增序列

这题比较简单:

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int len=nums.size();
        vector<int> dp(len,1);//最长的子序列长度至少是自身,故至少为1

        int maxLen=1;//长度至少也为1
        for(int i=1;i<len;i++){
            if(nums[i]>nums[i-1])dp[i]=dp[i-1]+1;
            maxLen=max(maxLen,dp[i]);
        }

        return maxLen;

    }
};

718.最长重复子数组

 

假如对于A的数组,和B的数组,其拥有一个长度为3最大公共子数组。

那么A数组从第i位到第i+2位,和B数组的第j位到第j+2位的应该是相等。

这意味着,A数组的第i位等于B数组的第j位,然后A数组的i+1位等于B数组的j+1位。

这意味着什么呢?这意味着假如我们用dp[i][j]存储A数组的前i位,和B数组的前j位的最大公共子数组,假如第i位和第j位相同,那么此时dp[i][j]=dp[i-1][j-1]+1

意思就是说假如前面已经有两个位置的字符相同了,那么走到这里时,相同的字符就是前面的2加上当前的1了。

(此处的dp[i][j]可以是代表第i位和第j位结尾的字符串所得到的最大长度,也可以是i-1位和j-1位代表的最大长度。

如果是前者,那么二维矩阵中,第0行第0列会代表字符串的第0个字符,并且需要我们自己去初始化第一行第一列。(其实本题的二维矩阵就是下面那种写法的矩阵的右下方罢了:如图红框内容所示:

本题该种解法代码如下:

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int len1=nums1.size();
        int len2=nums2.size();
        vector<vector<int>> dp(len1,vector<int>(len2));

        int maxLen=0;
        for(int i=0;i<len1;i++){
            if(nums2[0]==nums1[i]){maxLen=1;dp[i][0]=1;}
        }
        for(int j=0;j<len2;j++){
            if(nums1[0]==nums2[j]){maxLen=1;dp[0][j]=1;}
        }
        for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                if(nums1[i]==nums2[j])dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=0;
                maxLen=max(maxLen,dp[i][j]);
            }
        }
        return maxLen;
    }
};

如果是后者,则二维矩阵的第0行第0列不代表字符串的第0个,第一行第一列才代表字符串的第一个。此时我们不需要自己去初始化第0行第0列。

对于该dp值的二维数组该如何初始化?注意到我们需要使用到斜上一格,所以第一行第一列需要空出来初始化为0。效果如下:

 说说本题自己遇到的几个坑,首先就是,由于是计算的最长公共子数组的长度,首先就必须要明白,这个子数组必须是连续的!

另一方面,虽然

[1,2]和

[3,1,2]  、[3,1,2,4] 、 [3,1,2,4,5]

虽然和这几个数组的最长公共子数组都是[1,2]没错

但是要明白,本题的dp[i][j]值并不是代表0到i和0到j两个数组的所有子数组中所能达成的最长公共长度!(这也就算为什么返回值不是dp[len1-1][len2-1]了,而是在过程中找最大值(用maxLen来存储)

事实上,dp[i][j]代表的是以i为结尾的数组和以j结尾的数组的最大子数组的长度!(要明白这和上面的差别在于什么!)

可以看到在表格中,第一行只有一个1,(如果是子序列,那么出现了一个1以后,那么这一行后面的dp值只可能变大,不可能变小。 

因为只有这样才能保证dp[i][j]由前面的dp[i-1][j-1]转移过来的时候是连续相等的情况(即子数组而非子序列)

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int len1=nums1.size();
        int len2=nums2.size();
        vector<vector<int> > dp(len1,vector<int> (len2));
        /*//初始化第一列 
        for(int i=0;i<len1;i++){
            if(nums1[i]==nums2[0]){
                for(int k=i;k<len1;k++)dp[k][0]=1;
                break;
            }
        }

        //初始化第一行
        for(int j=0;j<len2;j++){
            if(nums1[0]==nums2[j]){
                for(int k=j;k<len1;k++)dp[0][k]=1;
                break;
            }
        }*/

        int maxLen=0;
        for(int i=0;i<len1;i++){
            if(nums1[i]==nums2[0]){
                maxLen=1;//初始化第一行时此时也有可能最长的长度是1,不要忘了!
                dp[i][0]=1;
            }
        }

        //初始化第一行
        for(int j=0;j<len2;j++){
            if(nums1[0]==nums2[j]){
                dp[0][j]=1;
                maxLen=1;
            }
        }

        for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                if(nums1[i]==nums2[j]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                //else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);//对于子序列是这样
                //else dp[i][j]=0;
                maxLen=max(maxLen,dp[i][j]);
            }
        }



        return maxLen;
    }
};

还有一点容易遗忘的是:

1143.最长公共子序列

 

 

 

注意,本题不要求连续,而前面那题子数组要求连续这意味着dp[i][j]的大小必须是以(i,j)结尾的,而本题不要求连续,这意味着dp[i][j]得到最大值时,不一定是以(i,j)结尾的

意味着dp[i][j]得到最大值时,不一定是以(i,j)结尾的,所以可以是

 

准确来说当str1[i]和str2[j]不相等时,dp的状态方程应为:

                else dp[i][j]=max(dp[i][j-1],dp[i-1][j],dp[i-1][j-1]);

还应该包含第三个状态,但是左方和上方的数,一定大于等于左上方的数,所以可以忽略这个状态,

class Solution {
public:
    int longestCommonSubsequence(string nums1, string nums2) {
        int len1=nums1.size();
        int len2=nums2.size();
        vector<vector<int> > dp(len1,vector<int> (len2));

        int maxLen=0;

        //初始化第一列 
        for(int i=0;i<len1;i++){
            if(nums1[i]==nums2[0]){
                maxLen=1;
                for(int k=i;k<len1;k++)dp[k][0]=1;
                break;
            }
        }

        //初始化第一行
        for(int j=0;j<len2;j++){
            if(nums1[0]==nums2[j]){
                maxLen=1;
                for(int k=j;k<len2;k++)dp[0][k]=1;
                break;
            }
        }


        for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                if(nums1[i]==nums2[j]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);//对于子序列是这样
                maxLen=max(maxLen,dp[i][j]);
            }
        }

        //return maxLen;
        return dp[len1-1][len2-1];
    }
};

1035.不相交的线

 

 

53.最大子序和

 

 

 

编辑距离

392.判断子序列

双指针思路较为简单,此处不在双指针这里过多的赘述。

除此之外,本题与前面的1143.求最长公共子序列的题目十分相像,区别在于1143是求公共长度,本题是求其中一个是否为另外一个的子序列。

那其实很简单,只需要在1143题的代码最下方最终判断一下是否满足公共子序列长度是短的字符串那个的长度即可:

 

class Solution {
public:
    bool isSubsequence(string nums1, string nums2) {
        int len1=nums1.size();
        int len2=nums2.size();
        if(len1==0)return true;        
        if(len2==0)return false;

        vector<vector<int> > dp(len1,vector<int> (len2));

        int maxLen=0;

        //初始化第一列 
        for(int i=0;i<len1;i++){
            if(nums1[i]==nums2[0]){
                maxLen=1;
                for(int k=i;k<len1;k++)dp[k][0]=1;
                break;
            }
        }

        //初始化第一行
        for(int j=0;j<len2;j++){
            if(nums1[0]==nums2[j]){
                maxLen=1;
                for(int k=j;k<len2;k++)dp[0][k]=1;
                break;
            }
        }


        for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                if(nums1[i]==nums2[j]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);//对于子序列是这样
            }
        }

        return dp[len1-1][len2-1]==len1;
    }
};


可以优化的地方:

由于本题第一个字符串一定比第二个字符串短,

所以如果nums1[i]和nums2[j]不相等,

此时dp值最大的来源肯定是dp[i][j-1]而不会是dp[i-1][j]

因此                                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
 

可以变为                else dp[i][j]=dp[i][j-1];

可能还得考虑空集这样特殊的例子:

于是乎需要加入这个条件:

 

115.不同的子序列

 

 

 

(dp[i][j]也可以是代表以i结尾的子串和以j结尾的子串的最大方法数。)

假设不相等,则只能由s[i-2]去和t[j-1]去匹配,因此此时dp[i][j]=dp[i-1][j]

如果相等,那么此时可以由s[i-1]和t[j-1]去匹配,也可以用s[i-2]去和t[j-1]做匹配,也就是多了一种选择。因此此时dp[i][j]=dp[i-1][j]+dp[i-1][j-1]

例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不⽤s[3]来匹配,即⽤ s[0]s[1]s[2]组成的bag去和t去匹配。而本题是要计算有多少种情况,所以可以由这两种的总方法数加起来。

二次复习时新补充的更便于理解的思路:

比如字符串bag和bxagg

首先就是,我们让i为行,让i代表的是短的那个字符串,然后j代表长的字符串。

我们可以以遍历的顺序来去思考:即一行一行从左到右去遍历 意思就是比如固定短的字符串为:bag,而长的字符串就是从b、bx、bxa、bxag、bxagx、bxagxg去不断加长的过程

另一方面,由于是在的子串j中找i的串,所以j一定大于i。因此,对于二维数组dp值,当j小于i时,dp[i][j]一定为0。

对于两个字符串的第i位和第j位,当i和j不相等时,就说明此时新的那个j的字符是没有用的,例如此时j从4到5,也就是从bxag变成了bxagx,那么此时新增的j对应的字符是x没法匹配上是无用的。于是即使无视这个第j个字符,结果不会变。所以dp值就继承了前面的值,也就是dp[i][j-1]。

但当如果第j个字符相等,即比如此时是bxagxg,此时第j个字符(最后一个)是匹配的,那么,由于是计算的情况数,所以需要把不同的情况累加起来。

首先情况数肯定不会减小,只会增加,增加的数额如何计算呢?那就是,我们都去排除第i个字符和第j个字符,看0到i-1的的字符串和0到j-1的字符串的dp值是多大,加上这个值即可。因此此时

dp[i][j]=dp[i][j-1]+dp[i-1][j-1]

(为什么没有dp[i-1][j]呢?很简单,我觉得就是没有关系

(这也解释了为什么动规的转移方程时不需要考虑dp[i-1][j]

(下面这个解析的i是长字符,j才是短字符串,与我上面的不同

3. dp数组如何初始化

从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][0] 和dp[0][j]是 ⼀定要初始化的。

其实此处初始化比较好理解,只要判断第0列和第0行的情况,判断这种情况,t是否是s的子集即可。看下图即可知道怎么初始化:

于是最终代码如下:

583.两个字符串的删除操作

 本题和动态规划:115.不同的⼦序列相⽐,其实就是两个字符串可以都可以删除了,情况虽说复杂⼀ 些,但整体思路是不变的。

动规的dp方程怎么想呢?题目要我们求什么,我们就设定dp值为什么!

要求最小步数,我们就设定dp值为最小步数。

上面的这个+1和+2是哪里来的?就[j]是删除字符得来的,删除第i-1个字符,这个操作就是1次,然后再加上之前的操作次数,即dp值[i-1]

 

 

72.编辑距离

 

 

其实删除元素还有一种,就是两个字符串各自删除一个当前字符,但是这种情况不如替换字符。因为删两个字符和替换一个字符,最终效果一样。

添加元素的本质上和删除元素是相同的。

 

 

 

 

 

回文子串

回文子串的dp值的表达式,遍历顺序,dp推导公式都与其他上面的那些题不是一个类型,因此多开一个板块。

647.回文子串

 

 

注意,此处的i是从大到小,而j是从小到大。

并且j是要从i出发,因为需要计算j-i的值。

这种循环的实际意义思路就是说:对于字符串abcdefedc来说,先从最后一个结点开始,然后往两边扩散,判断是否是回文,再从倒数第二个结点往中间扩散。对于每个节点往中间扩散

完整代码如下:

动态规划法复杂度较高,此处看看双指针法。

516.最长回文子序列

  子序列不要求连续!!!

(因为是子序列,并不要求连续,所以可以单独延伸一边,得到一个最大值) 

 

顺序:从下到上,从左到右

回溯法

 698.划分k个相等的子集

本题的回溯法可以参考这篇文章:

经典回溯算法:集合划分问题「重要更新 🔥🔥🔥」 - 划分为k个相等的子集 - 力扣(LeetCode)

代码:

以前回溯时,我们只需要一个sum即可,比如上面那题分为两个等和子集,实际上只需要考虑一个即可。但是本题有k个,所以我们可以设置k个桶,然后每次dfs时使用一个球,将其放到1~k个桶里面。

对于本题不能使用全局变量findFlag的形式,因为我们一旦找到,即可停止搜索:

此外这里还有三个剪枝的方法,具体可以看上面那篇文章讲的很详细。

class Solution {
public:
    int sum=0;
    int target;
    int len;
    vector<int> num;
    vector<int> buckets;

    bool canPartitionKSubsets(vector<int>& nums, int k) {
        num=nums;
        len=nums.size();
        for(auto val:nums)sum+=val;
        if(sum%k!=0)return false;
        buckets.resize(k);
        target=sum/k;
        
        return dfs(0);
    }
    bool dfs(int index){
        if(index==len){
            for(int i=0;i<buckets.size();i++){
                if(buckets[i]!=target)return false;
            }
            return true;
        }

        for(int i=0;i<buckets.size();i++){
            if(buckets[i]+num[index]>target)continue;
            buckets[i]+=num[index];

            if(dfs(index+1))return true;
            buckets[i]-=num[index];
        }
        return false;
    }

};

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

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

相关文章

使用sapply函数改写for循环并绘制迟滞温度与污染物效应图

For循环应该是我们在R语言使用得最普遍的循环了&#xff0c;优势就是简单、易上手&#xff0c;缺点就是慢&#xff0c;特别对于跑数据量比较大的数据。Apply家族函数使用C来编写&#xff0c;运行得非常快&#xff0c;非常适合代替for循环。今天介绍一下sapply函数改写for循环并…

abp.net 5.0 部署IIS10

今天遇到了abp.net 5.0部署iis10被卡住的问题&#xff0c;网上找了一些资料&#xff0c;都不是我要的&#xff0c;最后我总结一下我用的是 5.0的版本&#xff0c;所以我需要给服务器安装 iis5.0的相关运行环境 1&#xff1a;https://dotnet.microsoft.com/zh-cn/download/dotne…

html--学习

javascrapt交互&#xff0c;网页控制JavaScript&#xff1a;改变 HTML 图像本例会动态地改变 HTML <image> 的来源&#xff08;src&#xff09;&#xff1a;点亮灯泡<script>function changeImage() {elementdocument.getElementById(myimage) #内存变量&#xff0…

Linux---基本指令

专栏&#xff1a;Linux 个人主页&#xff1a;HaiFan. 基本指令ls 指令pwd命令cd 指令touch指令mkdir指令&#xff08;重要&#xff09;rmdir指令 && rm 指令&#xff08;重要&#xff09;man指令&#xff08;重要&#xff09;cp指令&#xff08;重要&#xff09;mv指令…

win10 C++调用conda的python

普通 比如说是conda的DL环境&#xff0c;路径是D:\Miniconda3\envs\DL VC目录->包含目录里加入D:\Miniconda3\envs\DL\include VC目录->库目录里加入D:\Miniconda3\envs\DL\libs 链接器->输入->附加依赖项里加入D:\Miniconda3\envs\DL\libs\python37.lib 在l…

“ 寻友之旅 “ 的三种解决办法

题目来源于&#xff1a;稀土掘金 " 寻友之旅 " 的三种解决办法&#xff01; 本文将分别讲解如何使用BFS、双向BFS以及 Dijsktra堆优化的方法来解决此题~ 一起来看看吧&#xff01; 附Java题解代码&#xff01; 文章目录" 寻友之旅 " 的三种解决办法&#…

如何将两个或多个PDF文件合并成一个?这3个方法可以看看

在工作中&#xff0c;有时候我们需要把两个或多个PDF文件合并成一个&#xff0c;这样一来&#xff0c;可以方便阅读、修改&#xff0c;还能快速打印文件。 下面分享3个工具&#xff0c;看看如何将两个或多个PDF文件合并成一个文件。 方法一&#xff1a;使用美图工具 如果PDF文…

【Spring AOP】如何统一“拦截器校验、数据格式返回、异常返回”处理?

目录 一、Spring 拦截器 1.1、背景 1.2、实现步骤 1.3、拦截原理 二、 统一url前缀路径 2.1、方法一&#xff1a;在系统的配置文件中设置 2.2、方法二&#xff1a;在 application.properies 中配置 三、统一异常处理 四、统一返回数据返回格式处理 4.1、背景 4.2、…

PTA:L1-025 正整数A+B、L1-026 I Love GPLT、L1-027 出租(C++)

目录 L1-025 正整数AB 问题描述&#xff1a; 实现代码&#xff1a; L1-026 I Love GPLT 问题描述&#xff1a; 实现代码&#xff1a; L1-027 出租 问题描述&#xff1a; 实现代码&#xff1a; 原理思路&#xff1a; 出租那道题有点意思哈 L1-025 正整数AB 问题描述…

【Java学习笔记】13.Java StringBuffer 和 StringBuilder 类

Java StringBuffer 和 StringBuilder 类 当对字符串进行修改的时候&#xff0c;需要使用 StringBuffer 和 StringBuilder 类。 和 String 类不同的是&#xff0c;StringBuffer 和 StringBuilder 类的对象能够被多次的修改&#xff0c;并且不产生新的未使用对象。 在使用 St…

Tomcat8安装

1、前置环境 Tomcat 8 对应jdk 1.8 版本&#xff1b;如果你的jdk版本是8以上&#xff0c;则安装对应的tomcat版本。 jdk8 官方下载安装时&#xff0c;先安装jdk版本&#xff0c;最后单独安装jre。所以电脑会有两套jre&#xff0c;一套是jdk中的jre&#xff0c;位于 \jre 目录下…

客户案例|三强联手,深度集成,实现四方共赢

关键发现&#xff1a; 用户痛点&#xff1a;以现有ERP系统台账表单模式管理设备&#xff0c;已经不能满足伯恩业务增长所需的设备管理优化与革新的要求。 解决方案&#xff1a;利用西门子Mendix低代码平台与SAP PM模块进行集成开发的联合解决方案&#xff0c;为实现客户设备资…

3.8 并查集

并查集 题目链接 用途 维护集合 将两个集合合并询问两个元素是否在一个集合当中 实现思路 用树的形式维护集合每个集合用一棵树表示&#xff0c;树根的编号就是整个集合的编号&#xff0c;每个节点存储他的父节点&#xff0c;p[x]表示节点x的父节点判断树根的方法:p[x]x求…

运维视角:rabbitmq教程(三)镜像集群

上期回顾 RabbitMQ集群中节点包括内存节点、磁盘节点。内存节点就是将所有数据放在内存&#xff0c;磁盘节点将数据放在磁盘上。如果在投递消息时&#xff0c;打开了消息的持久化&#xff0c;那么即使是内存节点&#xff0c;数据还是安全的放在磁盘。那么内存节点的性能只能体现…

如何编写BI项目之ETL文档

XXXXBI项目之ETL文档 xxx项目组 ------------------------------------------------1---------------------------------------------------------------------- 目录 一 、ETL之概述 1、ETL是数据仓库建构/应用中的核心…

Linux基础命令-kill向进程发送信号

Linux基础命令-setfacl设置文件ACL策略规则 Kill 一.命令介绍 先使用帮助文档查看命令的信息 NAME kill - terminate a process kill命令的主要功能就是向进程发送信号&#xff0c;这里我们主要用来终止结束进程的&#xff0c;与它的英文单词含义相同&#xff0c;在Linux系统…

matlab在管理学中的应用简matlab基础【三】

规划论及MATLAB计算 1、线性规划 问题的提出 例1. 某工厂在计划期内要安排甲、乙两种产品的生产&#xff0c;已知生产单位产品所需的资源A、B、C的消耗以及资源的计划期供给量&#xff0c;如下表&#xff1a; 问题&#xff1a;工厂应分别生产多少单位甲、乙产品才能使工厂获…

相亲交友直播APP源码

一、什么是亲交友直播APP源码&#xff1f; 亲交友直播APP源码是一款婚恋交友类型的APP&#xff0c;可以帮助单身男女在网络平台就可以进行相亲交友。APP源码分两端&#xff0c;一端是用户端&#xff0c;另外一端是后台端。采用的技术&#xff0c;前端是安卓IOS&#xff0c;后端…

SCI期刊写作必备(二):代码|手把手绘制目标检测领域YOLO论文常见的性能对比折线图,一键生成YOLOv7等主流论文同款图表,包含多种不同功能风格对比图表

绘制一个原创属于自己的YOLO模型性能对比图表 具体绘制操作参考:(附Python代码,直接一键生成,精度对比图表代码 ) 只需要改动为自己的mAP、Params、FPS、GFlops等数值即可,一键生成 多种图表风格📈,可以按需挑选 文章目录 绘制一个原创属于自己的YOLO模型性能对比图…

二、HTTP协议02

文章目录一、HTTP状态管理Cookie和Session二、HTTP协议之身份认证三、HTTP长连接与短连接四、HTTP中介之代理五、HTTP中介之网关六、HTTP之内容协商七、断点续传和多线程下载一、HTTP状态管理Cookie和Session HTTP的缺陷无状态。Cookie和Session就用来弥补这个缺陷的。 Cooki…