算法【Java】 —— 滑动窗口

news2024/11/16 4:52:04

滑动窗口

在上一篇文章中,我们了解到了双指针算法,在双指针算法中我们知道了前后指针法,这篇文章就要提到前后指针法的一个经典的使用 —— 滑动窗口,在前后指针法中,我们知道一个指针在前,一个指针在后,但是两个指针都是向前移动,如果两个指针之间的元素或者下标之间的差值有一些别的作用或者含义的时候(本质上就是子数组或者子串),我们会使用到滑动窗口这个算法思想,下面以第一道例题为引例。

题目实战

引例 —— 长度最小的子数组

https://leetcode.cn/problems/minimum-size-subarray-sum/

在这里插入图片描述

这道题目要求我们找到最少元素构成的子数组之间所有的数据总和大于等于 target , 也就是找子数组的题目,如果使用暴力美学,那么就需要枚举出所有的子数组,然后进行比较,时间复杂度为 O(N ^ 2)

暴力美学也是使用的是前后指针法,但是由于没有利用好递增关系,所以你也可以认为滑动窗口也是前后指针法的进阶,我们先这样看,使用一个指针遍历数组,然后使用 count 来统计遍历过的元素之和,由于数组的元素是正整数,所以 count 会随着 指针的移动而增加,在数学上,我们认为这是一种递增关系。

这样我们可以先让一个指针遍历数组(这个可以叫进窗口),直到 count >= target 的时候,就需要更新最短的长度,以及让 后指针 向前移动(这个可以叫出窗口),直到 count < target,此时这个循环会让count 减小,这也就让长度减小,所以要在循环中也顺便更新最短的长度。这就是滑动窗口的思路

思考为什么前指针不需要后退,而是直接向前遍历即可?
在暴力美学中,前指针都是回到后指针前一个位置,然后开始继续向前遍历计算新的 count,但是由于具有递增关系,前指针回退是没有必要的,因为已经计算好当前的 count 了,你的回退反而浪费原本的count 这个数值。只需要继续向前遍历即可,我们这个 count 在滑动窗口是动态变化的,根据前指针移动而增加,根据后指针移动而减少。
所以滑动窗口是对前后指针法的暴力美学的优化
在思考问题的时候,我们优先会想到暴力美学,因为它直观也容易想到,如果符合滑动窗口的特性,我们就可以尝试优化。

总结一下滑动窗口的算法思想:
1.进窗口
2.出窗口
3.更新答案(下面是说成数据)(根据题意调整具体位置)

时间复杂度为O(2N) = O(N)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int len = nums.length;
        int count = 0;
        int ans = Integer.MAX_VALUE;
        for(int left = 0, right = 0; right < len; right++) {
            //进窗口
            count += nums[right];

            //出窗口
            while(count >= target) {
                ans = Math.min(ans, right - left + 1); //更新数据
                count -= nums[left++];
            }
        }

        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

无重复字符的最长子串

https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/

在这里插入图片描述

解析:
如果使用暴力美学,还是前后指针法,由于这是求最长的字符串(也就是子串问题),对于子串子数组我们优先会想到滑动窗口算法。

在计算不重复的字符串的时候,前指针遇到不重复的字符,两个指针之间的距离就会自增,如果遇到重复的字符,后指针就会向前移动进行去重,按照暴力美学,前指针应该回退到后指针的前一个位置,但是有必要回退吗?首先因为经过后指针的去重,此时前后指针之间就没有重复的字符,这时候没有必要前指针回退,而这时候就是滑动窗口算法的特点。

使用滑动窗口,将不重复的字符加入到窗口中,当遇到重复的字符后指针就向前移动进行去重,再进行完进窗口和出窗口后,我们就可以更新最长长度了。

如何去重呢?大家可以使用Java 给我们提供的 HashSet,也就是哈希表进行去重。

补充:字符串转化成 字符数组的 方法 是 toCharArray().

本题使用 滑动窗口 + 哈希表,时间复杂度为O(2N) = O(N)

class Solution {
    public int lengthOfLongestSubstring(String ss) {
        int ans = Integer.MIN_VALUE;
        char[] s = ss.toCharArray();
        int len = s.length;
        Set<Character> set = new HashSet<>();
        for(int left = 0, right = 0; right < len; right++) {
            char ch = s[right];
            
            if(!set.contains(ch)) {
                set.add(ch); //进窗口
            } else {
                while(ch != s[left++]) {
                    set.remove(s[left-1]);//出窗口
                }
            }

            //更新数据
            ans = Math.max(ans,right - left + 1);
        }
        return ans == Integer.MIN_VALUE ? 0 : ans;
    }
}

最大连续1的个数

https://leetcode.cn/problems/max-consecutive-ones-iii/description/

在这里插入图片描述

解析:
由于这是字数组问题,可以考虑滑动窗口,由于题目给定的 K 是最大零数字的承载量,所以如果超过这个量就需要进行去重操作,后指针直接前移,在零元素的前一个位置停下即可,这是出窗口,进窗口的条件很简单就是小于等于承载量的时候,更新数据放在最后,这样就保证这是一个符合题意的子数组。

承载量如何判断?引入一个变量 count 来记录当前的零元素个数

class Solution {
    public int longestOnes(int[] nums, int k) {
        int ans = Integer.MIN_VALUE;
        int len = nums.length;
        int count = 0;
        for(int left = 0, right = 0; right < len; right++) {
            //进窗口
            if(nums[right] == 0) {
                count++;
            }

            //出窗口
            if(count > k) {
                while(nums[left++] != 0) {;}
                count--;
            }

            //更新数据
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}

将 x 减到 0 的最小操作数

https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/

在这里插入图片描述

解析:
由于每次只能减去最左端或者最右端的数值,所以如果最后有结果的话,那就是数组减去左区间和右边间,这两个区间的所有数字之和恰好等于 x
在这里插入图片描述
题目要求求出最小的操作数,说明左右区间的长度之和要最小。

如果正着求比较难的话,我们可以尝试反着求,也就是求中间部分最长,求中间部分一看就知道可以使用滑动窗口的方法。中间部分的数值应该等于 数组的所有数据之和减去 x ,我这里使用 target 变量来接受。

现在就是处理滑动窗口了,使用 sum 来接受此时 前后指针之间的所有的数据之和,先进窗口(sum 加数据),然后如果 sum > target 的时候就要出窗口(使用循环),最后更新数据的条件就是当 target == sum 时,进行更新数据。

细微条件处理:当数组的所有的数据之和小于 x 的时候,直接返回 -1 ,不能进入滑动窗口的部分,因为 target 计算后 sum - x = target < 0,在滑动窗口中 sum 始终大于 taget ,最终会一直出窗口,然后发生数组越界访问。

class Solution {
    public int minOperations(int[] nums, int x) {
        int sum = 0;
        for(int y : nums) {
            sum += y;
        }
        int target = sum - x;
        if(target < 0) {
            return -1;
        }
        int len = nums.length;
        int count = Integer.MIN_VALUE;
        sum = 0;
        for(int left = 0, right = 0; right < len; right++) {
            //进窗口
            sum += nums[right];

            //出窗口
            while(sum > target) {
                sum -= nums[left++];
            }

            //更新数据
            if(sum == target) {
                count = Math.max(count, right - left + 1);
            }
        }

        return count == Integer.MIN_VALUE ? -1 : len - count;
    }
}

水果成篮

https://leetcode.cn/problems/fruit-into-baskets/

在这里插入图片描述

解析:
最多只能装两种水果类型,题目要求我们要求出最大的采摘数量,显而易见的要使用滑动窗口。
进窗口,直接边遍历边进。
出窗口,就是遇到第三个水果类型,需要进行调整。
更新数据,在前两步骤做好就可以进行数据的更新了。

现在就要讨论用什么东西来装水果?
我们可以使用哈希表,Map 来接受水果类型和该水果目前的数量。

整体思路就是 哈希表加滑动窗口

class Solution {
    public int totalFruit(int[] fruits) {
        Map<Integer,Integer> map = new HashMap<>();
        int len = fruits.length;
        int count = Integer.MIN_VALUE;
        for(int left = 0, right = 0; right < len; right++) {
            //进窗口
            map.put(fruits[right],map.getOrDefault(fruits[right],0) + 1);

            //出窗口
            while(map.size() > 2) {
                map.put(fruits[left],map.get(fruits[left]) - 1);
                if(map.get(fruits[left]) == 0) {
                    map.remove(fruits[left]);
                }
                left++;
            }

            //更新数据
            count = Math.max(count, right - left + 1);
        }

        return count;
    }
}

找到字符串中所有字母异位词

https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/

在这里插入图片描述

解析:
还是子串问题,使用滑动窗口。由于涉及到字符串的查找,所以我们可以使用 toCharArray() 方法将字符串转化为数组,便于我们的使用。

如何存储两个字符串的内容:我们会想到使用哈希表,使用Map 来接受 字母 —— 出现数量 这个键值对,但是题目说了字符串只包含小写字母,那我们可以自己创建两个数组来模拟哈希表,而且使用数组更加方便。

如何比较两个字符串的内容?很多老铁一定想过使用循环遍历,而且我们还是使用数组来模拟哈希表,那就更加方法,这里介绍一种更好的方法,不需要循环遍历就可以比较成功。
使用一个变量来接收有效的字符数量。

如何使用?
我们在进出窗口的过程中顺便统计有效字符的个数,什么是有效的字符,我们需要和另一个哈希表的内容进行判断
这也就说明了我们要事先制作好一个哈希表,然后才开始遍历字符串 s
在遍历过程中,s 的哈希表对应的下标的元素先自增,然后开始判断,当这个字符也存在于 p 的哈希表中,并且 s 对应的元素值要小于等于 p 哈希表的,就算作有效字符
如果是大于 p 的,说明这个字符要么是重复字符要么是 p 哈希表不存在的字符。

出窗口:什么时候要出窗口?答:当当前的字符串长度大于 字符串 p 的长度,因为我们在进窗口的时候,只会统计有效字符个数,当有效字符个数等于 p 字符串的时候,才会更新数据,而此时满足当前 前后指针夹着的字符串全是有效字符 这个条件,只要当前的字符串的长度大于 p 字符串的长度的时候,就一定不是有效字符串,这时候就可以出窗口,顺便调整 s 哈希表的内容,由于此时的调整可能会删除掉有效字符,所以这个也要考虑有效字符的数量的变化,如果你要删除的字符对应的 s 哈希表的数值 小于等于 p 哈希表的时候 —— 有效字符数量就要自减,并且出窗口的次数实际上就是一次,所以用不到循环。

class Solution {
    public List<Integer> findAnagrams(String ss, String pp) {
        List<Integer> list = new ArrayList<>();
        char[] s = ss.toCharArray();
        char[] p = pp.toCharArray();
        int[] hash1 = new int[26];
        int[] hash2 = new int[26];
        for(char x : p) {
            hash2[x-'a']++;
        }

        int len = s.length;
        int count = 0;
        int size = p.length;
        for(int left = 0, right = 0; right < len; right++) {
            char ch = s[right];
            //进窗口
            if(++hash1[ch-'a'] <= hash2[ch-'a']) {
                count++;
            }

            //出窗口
            if(right - left + 1 > size) {
                if(hash1[s[left]-'a']-- <= hash2[s[left]-'a']) {
                    count--;
                }
                left++;
            }
            
            //更新数据
            if(count == size) {
                list.add(left);
            }
        }

        return list;
    }
}

串联所有单词的子串

https://leetcode.cn/problems/substring-with-concatenation-of-all-words/description/

在这里插入图片描述

解析:
这道题目和上一道单词的异位词很相似,只不过这道题目是以单词为单位的。
不过我们如果将单词视为一个字符的话,思路和上面的一模一样。
使用两个哈希表存放单词。
如何划分字符串,由于 words 中每个单词的长度都是一致的,所以我们可以将字符串按照这个数量进行划分。

我们从暴力美学出发,如果要找出所有的子字符串,那么就需要两层循环。
在暴力美学的内层循环中我们要找到符合的子串,可以使用滑动窗口来求解,因为要比较的单词的长度都是一致的,所以我们可以按照单词的长度对字符串进行划分,也就是说在内层循环中使用滑动窗口的时候,指针每次移动都是加单词的长度

那么外层循环有必要遍历整个字符串吗?
答:没有必要,因为单词的长度是一致的,所以你单词的划分最多的情况就是单词的长度:
在这里插入图片描述
当外层循环遍历次数超过单词长度的时候,你会发现单词的划分和前面的循环重合,所以这些超过单词长度的循环次数是没有必要的,你如果硬要遍历的话,也可以,就是时间复杂度差了些。

所以继续优化的话,最外层循环次数就是单词的长度

最后就是要注意方法的使用了,如何划分字符串,使用 substring(), 哈希表的使用方法要注意 getOrDefault(),如果不存在的话可以返回默认值,但是不会加入到哈希表,其他的就不说了,不了解这些方法的,可以去我往期文章中查阅:String 类
JavaDS —— 二叉搜索树、哈希表、Map 与 Set

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> list = new ArrayList<>();
        Map<String,Integer> mapW = new HashMap<>();
        for(String x : words) {
            mapW.put(x,mapW.getOrDefault(x,0) + 1);
        }

        int lenS = s.length();
        int len = words[0].length();
        Map<String,Integer> mapS = new HashMap<>();
        int count = 0;
        int size = words.length;
        for(int i = 0; i < len; i++) {
            mapS.clear();
            count = 0;
            for(int left = i, right = i; right + len <= lenS; right+=len) {
                String str = s.substring(right,right+len);
                mapS.put(str,mapS.getOrDefault(str,0) + 1); //进窗口

                if(mapS.get(str) <= mapW.getOrDefault(str,0)) {
                    count++;
                } 

                //出窗口
                if(right - left + 1 > len * size) {
                    String ch = s.substring(left,left+len);
                    if(mapS.get(ch) <= mapW.getOrDefault(ch,0)) {
                        count--;
                    }
                    mapS.put(ch,mapS.get(ch)-1);
                    left += len;
                }

                //更新数据
                if(count == size) {
                    list.add(left);
                }
            }
        }

        return list;
    }
}

最小覆盖子串

https://leetcode.cn/problems/minimum-window-substring/

在这里插入图片描述

解析:
还是一个子串问题,使用滑动窗口,由于字符串全是英文字母组成,我们可以使用数组来模拟哈希表的映射关系。
这里我们可以使用 count 变量来统计有效字符个数。
因为题目要求出最小覆盖子串,所以难免会有重复或者不存在于 t 的字符出现,所以为了便于判断是否已经容纳好 t 的所有字符,减少循环出现,我们可以使用额外的变量来统计有效字符个数

进窗口:这个无需多言,直接进

出窗口和更新数据要小心点,当 count 等于有效字符个数的时候,我们要更新数据,那什么时候出窗口呢?如果按照之前的讨论进窗口——出窗口——更新数据是很难实现的,在最开始的时候,我就跟大家提到更新数据这个操作要按照实际情况来讨论,在这道题目就体现出来了。

首先更新完数据后,我们希望在下一个滑动窗口循环中能得到一个残缺的字符串,就是 count 要小于 有效字符总数这一个条件,所以在更新数据之后,我们可以进行出窗口的操作,要注意 count 的 变化,既然要达到 count 要小于 有效字符总数这个条件,我们可以在更新数据的时候写一个循环,正好随着出窗口的过程中一起将数据更新。

class Solution {
    public String minWindow(String ss, String tt) {
        String str = "";

        if(ss.length() < tt.length()) {
            return str;
        }

        int lenS = Integer.MAX_VALUE;
        int[] hash1 = new int[128];
        int[] hash2 = new int[128];
        char[] s = ss.toCharArray();
        char[] t = tt.toCharArray();
        for(char x : t) {
            hash2[x]++;
        }

        int len = s.length;
        int count = 0;
        int size = t.length;
        for(int left = 0, right = 0; right < len; right++) {
            //进窗口
            char ch = s[right];
            hash1[ch]++;
            if(hash1[ch] <= hash2[ch]) {
                count++;
            }   

            while(count == size) {
                //更新数据
                if(lenS > right - left + 1) {
                    lenS = right - left + 1;
                    str = ss.substring(left,right+1);
                }

                //出窗口
                char x = s[left++];
                if(hash1[x]-- <= hash2[x]) {
                    count--;
                }
            }
        }
        return str;
    }
}

小结

滑动窗口一般用于子数组或者子串问题上

滑动窗口的基本思路:
1.设置好两个指针 (left = 0, right = 0)
2.进窗口控制 right 指针
3.出窗口控制 left 指针
4.根据实际情况更新数据

一些注意事项:
如果发现字符串完全由字母组成,可以直接使用数组来模拟哈希表,这样做会方便很多
如果涉及到有效字符或者有效数字的时候,可以引入一个变量 来进行统计

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

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

相关文章

Page与自定义Components生命周期

自定义组件 自定义组件一般可以用@component,装饰,在结构体里面用build方法定义UI,或者用@builder装饰一个方法,来作为自定义组件的构造方法 而页面page一般用@Entry,和@component结合起来使用 页面生命周期方法: onPageShow:页面每次显示时触发 onPageHide:页面每次隐藏时…

【LeetCode每日一题】——662.二叉树最大宽度

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 广度优先搜索 二【题目难度】 中等 三【题目编号】 662.二叉树最大宽度 四【题目描述】 给…

《python语言程序设计》2018版第7章第05题几何:正n边形,一个正n边形的边都有同样的长度。角度同样 设计RegularPolygon类

结果和代码 这里只涉及一个办法 方法部分 def main():rX, rY eval(input("Enter regular polygon x and y axis:"))regular_num eval(input("Enter regular number: "))side_long eval(input("Enter side number: "))a exCode07.RegularPol…

C++入门——01类与对象

1.类 1.1.类的引入 C语言中&#xff0c;结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数。 struct Student {void SetStudentInfo(const char* name, const char* gender, int age){strcpy(_name, name);strcpy(_gen…

微信公众号批量上传、发布文章管理系统(Python版)

功能亮点 一键批量操作文章自动排版支持自定义文章数量适用于多号操作支持文章管理、查询、查看支持查询当前状态 适用对象 公众号运营批量文章上传发布矩阵号管理 部分关键代码及步骤 微信公众号后台的设置与开发栏目中的基本配置里获取appid和appsecret。 获取微信公众号…

软件测试 - 测试用例(设计测试用例的思路、万能公式、测试用例设计的方法)

一、测试用例 1.1 概念 测试用例&#xff08; Test Case &#xff09;是为了实施测试而向被测试的系统提供的一组集合&#xff0c;这组集合包含&#xff1a;测试环 境、操作步骤、测试数据、预期结果等要素。 1.2 编写测试用例 1&#xff09;excel 表格编写 笔试题的测试用例…

【Nginx】Nginx 安装(平滑升级和回滚)

一、 Nginx 概述 Nginx 介绍 Nginx &#xff1a; engine X &#xff0c; 2002 年开发&#xff0c;分为社区版和商业版 (nginx plus ) 2019 年 3 月 11 日 F5 Networks 6.7 亿美元的价格收购 Nginx 是免费的、开源的、高性能的 HTTP 和反向代理服务器、邮件代理服务器、以…

路由器VLAN配置(H3C)

路由器VLAN配置&#xff08;H3C&#xff09; 控制页面访问 路由器默认处于192.168.1.1网段&#xff08;可以短按reset重置&#xff09;&#xff0c;如果要直接使用需要设置静态IP处于同一网段&#xff1b; 对路由器进行配置也要将电脑IP手动设置为同一网段&#xff1b; 默…

音频剪辑软件哪个好用?五大音频剪辑软件分享

如果你正打算在家自学视频制作&#xff0c;那么恭喜你&#xff0c;你已经踏上了一段充满魔法与惊喜的旅程&#xff01;不过&#xff0c;别忘了&#xff0c;视频的灵魂不仅仅在于画面&#xff0c;更在于那直击心灵的音效。 想象一下&#xff0c;一个精心剪辑的片段&#xff0c;…

如何拯救非正常专利申请?

在无忧专利微信公众号2023年年初的一篇文章中提到&#xff0c;出于提质增效的考虑&#xff0c;专利局加大了对非正常申请的打击力度。 专利局打击非正常申请的方式包括&#xff1a;建立黑名单、启用新的业务办理系统、使用大数据识别技术、惩罚非正常申请和非正常代理行为。 …

Vehicle Perception from Satellite(2024 TPAMI 卫星视频车流量监控)

Vehicle Perception from Satellite&#xff08;2024 TPAMI 卫星视频车流量监控&#xff09; 前言1.1 动机1.2 概述1.3 贡献 2 相关工作2.1 遥感中的目标检测2.2 计算机视觉中的相关任务2.3 卫星交通监控 3 TMS 数据集3.1 数据收集与预处理3.2 数据统计3.3 应用任务 4 实验4.1 …

C++奇迹之旅:手写vector模拟实现与你探索vector 容器的核心机制与使用技巧

文章目录 &#x1f4dd;基本框架&#x1f320; 构造和销毁&#x1f309;vector()&#x1f309;vector(const vector& v)&#x1f309;vector(size_t n, const T& value T())&#x1f309;赋值拷贝构造&#xff1a;vector<T>& operator(vector<T> v)&a…

XSS的DOM破坏

目录 1、DOM破坏案例解释 1.1先写一个demo.html文件 1.2、执行demo&#xff0c;看到是否将全部移除%20scr1%20οnerrοralert(1)> 分析&#xff1a; 解决&#xff1a;把两个for不在同一个数组进行操作 1.3、接下来我们要想办法让第二个for循环不能删除&#xff0c;留下…

android FD_SET_chk问题定位

android FD_SET_chk问题定位 一、FD报错二、问题定位2.1 APM定位2.2 adb定位2.3. 代码获取FD数 三、FD优化 一、FD报错 App在运行中记录报错如下&#xff0c;FD_SET&#xff0c;这个问题大概是文件描述符&#xff08;File Descriptor&#xff0c;简称FD&#xff09;超过了最大…

首款会员制区块链 Geist 介绍

今天&#xff0c;Pixelcraft Studios 很高兴地宣布即将推出 Geist&#xff0c;这是一个由 Base、Arbitrum、Alchemy 以及 Aavegotchi 支持的全新 L3。 Geist 之前的代号为 “Gotchichain”&#xff0c;是首个专为游戏打造的会员专用区块链。 为什么选择 Geist&#xff1f; …

Spring DI 简单演示三层架构——Setter 注入

Spring IOC 的常见注入方法有3种&#xff1a;Setter注入、构造注入和属性注入。想了解更多可点击链接&#xff1a;Spring 注入、注解以及相关内容补充 属性注入 不推荐。原因&#xff1a;使用私有的成员属性变量&#xff0c;依靠反射实现&#xff0c;破坏封装&#xff0c;只能依…

~Keepalived高可用集群~

一、Keepalived简介 是一个用于实现高可用性的解决方案&#xff0c;它主要应用于云主机的主备切换&#xff0c;以达到高可用性&#xff08;HA&#xff09;的目的。当主服务器发生故障无法对外提供服务时&#xff0c;动态将虚拟IP切换到备服务器&#xff0c;继续对外提供服务&a…

DOM破坏

XSS Game 1、第一关 Ma Spaghet! <!-- Challenge --> <h2 id"spaghet"></h2> <script>spaghet.innerHTML (new URL(location).searchParams.get(somebody) || "Somebody") " Toucha Ma Spaghet!" </script> S…

【ubuntu24.04】wget配置代理加速下载

参考之前的wget代理配置 wget速度非常慢 配置控制台代理不行 配置wget代理 本机部署了代理程序:all_proxy 不识别:root@PerfSvr:~# cat set65proxy.sh #!/bin/sh export

[STM32F429_硬件知识01]

知识点1 &#xff1a;J-Link的使用步骤&#xff1a; step1 : 安装J-Link驱动程序 step2 : keil的魔术棒中 -> Debug -> Use中选择J_Link ->点击 Settings ->