栈和队列
题20 有效的括号
链接: link
括号匹配是栈的经典应用。
- 首先,只有偶数个括号才有可能匹配成功;
- 遍历时如果遇到了左括号,就将对应右括号入栈;
- 如果是右括号,但此时栈为空,则直接返回false;
- 如果是右括号,且栈不空,那么就需要弹出栈顶元素进行比较,相同则继续遍历,否则返回false。
- 最终遍历完,栈为空则说明匹配成功。
题155 最小栈
链接: link
本题需要一个常规栈和一个辅助栈,辅助栈每时每刻存放的都是当前元素和辅助栈顶的最小值。
题394 字符串解码
链接: link
本题难在要处理括号嵌套的情况,需要使用辅助栈。用Python来写本题更方便。
stack, res, multi = [], "", 0
for c in s:
if c == '[': # 如果是左括号,就需要入栈
stack.append([res, multi]) # 将当前的res和数字入栈
res, multi = "", 0 # 并置空置0
elif c == ']': # 右括号说明要进行计算了
last_res, cur_multi = stack.pop() # 将栈顶弹出
res = last_res + cur_multi * res # 计算本次括号内和数字的乘积,以及加上到上一个‘[’的字符
elif '0' <= c <= '9':
multi = multi * 10 + int(c) #将数字字符变为int值,注意可能是多位数字
else:
res += c #是字符就直接添加到res尾
return res
题239 滑动窗口最大值
链接: link
本题需要使用双端队列来维护一个单调递减队列。
int[] res = new int[nums.length-k+1]; //记录结果
Deque<Integer> deque = new LinkedList<>(); // 双端队列
for(int i=0; i<nums.length; i++){
if(!deque.isEmpty() && deque.peekFirst()==i-k){ // 队列满了要移除队头
deque.pollFirst();
}
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]){ // 当前元素比队尾大,就要一直移除队尾,保持单调递减
deque.pollLast();
}
deque.offerLast(i); // 当前元素入队
if(i >= k-1) res[i-k+1] = nums[deque.peekFirst()]; // 记录结果,队头元素即为最大值
}
return res;
哈希表
题1 两数之和
链接: link
要快速判断一个数的对应数是否在数组中出现,想到用哈希表。遍历数组,使用map.containsKey(target-nums[i])来判断对应数是否出现过,是则返回结果,否则将map.put(nums[i], i);当前数加入哈希表。
题49 字母异位词分组
链接: link
本题能想到用哈希表来做,但用“a3b2”的形式作为key还是很巧妙的。
HashMap<String, List<String>> map = new HashMap<>();
for(String s : strs){ //对strs中每个字符串s进行遍历
int[] count = new int[26]; // 统计s中每个字母的次数
for(int i=0; i<s.length(); i++){
count[s.charAt(i)-'a']++;
}
StringBuilder sb = new StringBuilder();
for(int i=0; i<26; i++){ // 将s变成的“a3b2”的形式
if(count[i]!=0){
sb.append((char) ('a'+i));
sb.append(count[i]);
}
}
String key = sb.toString(); // 以String的方式存为key值
// 找到map中key对应的值(字符串列表),如果为空就新建一个字符串列表
List<String> value = map.getOrDefault(key, new ArrayList<String>());
value.add(s); // 往字符串列表里添加当前遍历的字符串s
map.put(key, value); // 更新哈希表中key对应的值
}
return new ArrayList<List<String>>(map.values()); // 将哈希表的值返回
题128 最长连续序列
链接: link
本题我们并不在意相同的数字出现了几次,而是在意该数字的相邻数是否出现,所以本题需要去重和使用哈希表,而能去重的HashSet底层是哈希表,满足我们的需求。我们首先要判断当前数的前一个数是否存在,若存在则跳过本次枚举,若不存在,则可以尝试从该数开始往后依次枚举。
Set<Integer> set = new HashSet<>();
for(int num : nums){
set.add(num);
}
int res = 0;
for(int num : set){
if(!set.contains(num-1)){
int currentNum = num;
int currentLen = 1;
while(set.contains(currentNum+1)){
currentNum++;
currentLen++;
}
res = Math.max(res, currentLen);
}
}
return res;
题347 前k个高频元素
链接: link
频率可以用哈希表统计出来,前k个可以借助小根堆实现。
- 使用哈希表map统计出来每个元素的次数;
- 创建小根堆,以频率为排序依据;
PriorityQueue<Integer> pq = new PriorityQueue<>(
k, (a, b)->(map.get(a) - map.get(b))
);
- 进堆的是key值,小根堆未满可以直接进,满了就需要比较当前key的频率和堆顶的频率,当前更大就进堆;
- 最后输出堆中元素。
题438 找到字符串中所有字母异位词
链接: link
滑动窗口的方式找出所有下标,窗口大小就是p字符串的长度。如果s的长度小于p,显然不可能有解。
- 统计出s的初始窗口中和p中,分别字符出现次数。如果Arrays.equals(sCount, pCount),就说明初始窗口满足条件,添加下标0到结果中;
- 接着开始移动窗口,每次要将最左端字符移除,并将最右端字符加入,然后比较两个数组是否相等,如果相同则将窗口的左边界下标加入结果。
题560 和为K的子数组
链接: link
用前缀和+哈希表来解决,很巧妙。
HashMap<Integer, Integer> map = new HashMap<>();
int res = 0, presum = 0;
map.put(0, 1);
for(int i=0; i<nums.length; i++){
presum += nums[i];
if(map.containsKey(presum-k)) res += map.get(presum-k);
map.put(presum, map.getOrDefault(presum, 0)+1);
}
return res;
树
题94 二叉树的中序遍历
链接: link
递归法:
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root==null) return;
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
迭代法:
stack, res = [root], []
while stack:
i = stack.pop()
if isinstance(i, TreeNode): # 如果i是结点类型,说明是第一次访问,就需要对左右子树及自身入栈
stack.extend([i.right, i.val, i.left])
elif isinstance(i, int): # 如果i是int值,说明已经访问过一次,可以加到结果中
res.append(i)
return res
题96 不同的二叉搜索树
链接: link
这道题可以转换成,从1到n个数中,任选一个数j作为根节点,然后以1到j-1构造左子树,以j+1到n构造右子树,总的方法数。可以利用dp数组保存已经计算过的值,状态转移方程dp[i] += dp[j-1] * dp[i-j]。
题98 验证二叉搜索树
链接: link
二叉搜索树按中序遍历是递增的。可以在遍历的同时,设置一个前结点,如果前结点>=当前结点的值,则返回false。
TreeNode pre = null;
public boolean isValidBST(TreeNode root) {
if(root==null) return true;
boolean left = isValidBST(root.left);
if(pre!=null && pre.val>=root.val) return false;
pre = root;
boolean right = isValidBST(root.right);
return left && right;
}
题101 对称二叉树
链接: link
注意两棵树对称比较的是left.left和right.right;left.right和 right.left。
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return traversal(root.left, root.right);
}
boolean traversal(TreeNode left, TreeNode right){
if(left==null && right==null) return true;
else if(left==null || right==null) return false;
else if(left.val != right.val) return false;
return traversal(left.left, right.right) && traversal(left.right, right.left);
}
题102 二叉树的层序遍历
链接: link
借助队列实现。
List<List<Integer>> res = new ArrayList<>();
if(root==null) return res;
Deque<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> path = new ArrayList<>();
int len = que.size();
for(int i=0; i<len; i++){
TreeNode cur = que.poll();
path.add(cur.val);
if(cur.left!=null) que.offer(cur.left);
if(cur.right!=null) que.offer(cur.right);
}
res.add(new ArrayList<>(path));
}
return res;
题104 二叉树的最大深度
链接: link
后序遍历,因为要借助子树返回的结果。
public int maxDepth(TreeNode root) {
if(root==null) return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return 1+Math.max(left, right);
}
题105 从前序与中序遍历序列构造二叉树
链接: link
由于本题是要构造二叉树,所以采用先序遍历。构造流程:先根据先序序列第一个结点找到中序序列中的位置,然后对该数建立结点,也就是根节点,然后根据中序序列中root结点左右子树的结点数量去先序中进行划分,接着就可以递归的构造结点并划分区间。
HashMap<Integer, Integer> map;
public TreeNode buildTree(int[] preorder, int[] inorder) {
map = new HashMap<>();
for(int i=0; i<inorder.length; i++){
map.put(inorder[i], i);
}
return myBuild(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
public TreeNode myBuild(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd){
if(preStart>=preEnd || inStart>=inEnd) return null;
int rootIndex = map.get(preorder[preStart]);
TreeNode root = new TreeNode(inorder[rootIndex]);
int numOfLeft = rootIndex - inStart;
root.left = myBuild(preorder, preStart+1, preStart+1+numOfLeft, inorder, inStart, rootIndex);
root.right = myBuild(preorder, preStart+1+numOfLeft, preEnd, inorder, rootIndex+1, inEnd);
return root;
}
题124 二叉树中的最大路径和
链接: link
int maxSum = Integer.MIN_VALUE; // 用来保存最大路径和
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
public int maxGain(TreeNode root){
if(root==null) return 0;
int left = Math.max(maxGain(root.left), 0); // 左子树能提供的最大价值
int right = Math.max(maxGain(root.right), 0); // 右子树能提供的最大价值
maxSum = Math.max(maxSum, root.val+left+right); // 更新最大路径和
return root.val + Math.max(left, right); // root结点能够提供的最大价值
}
题226 翻转二叉树
链接: link
后序遍历。
public TreeNode invertTree(TreeNode root) {
if(root==null) return null;
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
TreeNode temp = left;
root.left = root.right;
root.right = temp;
return root;
}
题236 二叉树的最近公共祖先
链接: link
递归的终止条件是遇到叶子节点或者p或者q结点。如果left和right有一个为空,就说明最近公共祖先只能在另一棵子树取到;如果都不空,说明root结点就是最近公共祖先。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null || root==p || root==q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left==null) return right;
if(right==null) return left;
return root;
}
题297 二叉树的序列化与反序列化
链接: link
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if(root==null) return "X,"; // 空结点用X表示
String left = serialize(root.left);
String right = serialize(root.right);
return root.val + "," + left + right; // 根左右的顺序拼接起来
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] nodes = data.split(","); // 分隔字符串
Deque<String> queue = new LinkedList<>(Arrays.asList(nodes)); // 将数组转换成队列
return buildTree(queue);
}
public TreeNode buildTree(Queue<String> queue){
String value = queue.poll(); // 当前结点
if(value.equals("X")) return null;
TreeNode node = new TreeNode(Integer.parseInt(value)); // 构造结点
node.left = buildTree(queue); // 构建左子树
node.right = buildTree(queue); // 构建右子树
return node;
}
题337 打家劫舍3
链接: link
每个结点有偷和不偷两种状态,因此可以用一个长度为2的数组来保存偷或不偷该结点能得到的最大价值。
public int rob(TreeNode root) {
int[] res = robAct(root);
return Math.max(res[0], res[1]);
}
public int[] robAct(TreeNode root){
int[] res =new int[2];
if(root==null) return res;
int[] left = robAct(root.left);
int[] right = robAct(root.right);
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = root.val + left[0] + right[0];
return res;
}
题437 路径总和3
链接: link
本题采用的是哈希表+前缀和的方式,递归后要记得回溯。
HashMap<Long, Integer> prefixSum = new HashMap<>();
public int pathSum(TreeNode root, int targetSum) {
prefixSum.put(0L, 1);
return dfs(root, targetSum, 0);
}
public int dfs(TreeNode root, int target, long curSum){
if(root==null) return 0;
int res = 0;
curSum += root.val;
res += prefixSum.getOrDefault(curSum-target, 0);
prefixSum.put(curSum, prefixSum.getOrDefault(curSum, 0)+1);
res += dfs(root.left, target, curSum);
res += dfs(root.right, target, curSum);
prefixSum.put(curSum, prefixSum.get(curSum)-1);
return res;
}
题538 把二叉搜索树转换为累加树
链接: link
按右根左的顺序遍历即可。
int sum = 0;
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
public void dfs(TreeNode root){
if(root==null) return;
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
题543 二叉树的直径
链接: link
和题124最大路径和思路一致。
int maxLen = Integer.MIN_VALUE;
public int diameterOfBinaryTree(TreeNode root) {
maxGain(root);
return maxLen;
}
public int maxGain(TreeNode root){
if(root==null) return 0;
int left = maxGain(root.left);
int right = maxGain(root.right);
maxLen = Math.max(maxLen, left+right);
return Math.max(left, right)+1;
}
题617 合并二叉树
链接: link
if(root1==null) return root2;
if(root2==null) return root1;
root1.val += root2.val;
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
题208 实现Trie(前缀树)
链接: link
class Trie {
private Trie[] children;
private boolean isEnd;
public Trie() {
children = new Trie[26];
isEnd = false;
}
public void insert(String word) {
Trie node = this; // 指向根结点
for(int i=0; i<word.length(); i++){
char ch = word.charAt(i);
int index = ch - 'a';
if(node.children[index]==null){
node.children[index] = new Trie();
}
node = node.children[index];
}
node.isEnd = true;
}
public boolean search(String word) {
Trie node = this;
for(int i=0; i<word.length(); i++){
char ch = word.charAt(i);
int index = ch - 'a';
if(node.children[index]==null){
return false;
}
node = node.children[index];
}
return node.isEnd;
}
public boolean startsWith(String prefix) {
Trie node = this;
for(int i=0; i<prefix.length(); i++){
char ch = prefix.charAt(i);
int index = ch - 'a';
if(node.children[index]==null){
return false;
}
node = node.children[index];
}
return true;
}
}
图
题399 除法求值
链接: link
本题用并查集来解决。并查集的思想就是将不同元素的比值关系可以放到同一个根结点的集合中,最后求元素间的比值关系可以直接用边上的比值做除法。需要注意有一个路径压缩的操作:所有元素都直接和根结点相连。
以下代码来自力扣官方题解。
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
int equationsSize = equations.size();
UnionFind unionFind = new UnionFind(2 * equationsSize);
// 第 1 步:预处理,将变量的值与 id 进行映射,使得并查集的底层使用数组实现,方便编码
Map<String, Integer> hashMap = new HashMap<>(2 * equationsSize);
int id = 0;
for (int i = 0; i < equationsSize; i++) {
List<String> equation = equations.get(i);
String var1 = equation.get(0);
String var2 = equation.get(1);
if (!hashMap.containsKey(var1)) {
hashMap.put(var1, id);
id++;
}
if (!hashMap.containsKey(var2)) {
hashMap.put(var2, id);
id++;
}
unionFind.union(hashMap.get(var1), hashMap.get(var2), values[i]);
}
// 第 2 步:做查询
int queriesSize = queries.size();
double[] res = new double[queriesSize];
for (int i = 0; i < queriesSize; i++) {
String var1 = queries.get(i).get(0);
String var2 = queries.get(i).get(1);
Integer id1 = hashMap.get(var1);
Integer id2 = hashMap.get(var2);
if (id1 == null || id2 == null) {
res[i] = -1.0d;
} else {
res[i] = unionFind.isConnected(id1, id2);
}
}
return res;
}
private class UnionFind {
private int[] parent;
/**
* 指向的父结点的权值
*/
private double[] weight;
public UnionFind(int n) {
this.parent = new int[n];
this.weight = new double[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
weight[i] = 1.0d;
}
}
public void union(int x, int y, double value) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return;
}
parent[rootX] = rootY;
// 关系式的推导请见「参考代码」下方的示意图
weight[rootX] = weight[y] * value / weight[x];
}
/**
* 路径压缩
*
* @param x
* @return 根结点的 id
*/
public int find(int x) {
if (x != parent[x]) {
int origin = parent[x];
parent[x] = find(parent[x]);
weight[x] *= weight[origin];
}
return parent[x];
}
public double isConnected(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return weight[x] / weight[y];
} else {
return -1.0d;
}
}
}
回溯和DFS
题17 电话号码的字母组合
链接: link
不同集合中取元素的组合问题。注意这里backtrack(digits, startIndex+1)。
List<String> res = new ArrayList<>(); // 保存结果
StringBuilder path = new StringBuilder(); // 保存单条路径
String[] data = new String[] {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
if(digits==null || digits.length()==0) return res;
backtrack(digits, 0);
return res;
}
public void backtrack(String digits, int startIndex){
if(path.length()==digits.length()){ // path和digits一样长时开始保存结果
res.add(path.toString());
return;
}
String curStr = data[digits.charAt(startIndex) - '0']; // 当前可取字符串
for(int i=0; i<curStr.length(); i++){
path.append(curStr.charAt(i));
backtrack(digits, startIndex+1); // 进入下一个可取字符串
path.deleteCharAt(path.length()-1);
}
}
题22 括号生成
链接: link
本题关键在于理清什么时候可以添加左括号,什么时候可以添加右括号。
List<String> res = new ArrayList<>();
StringBuilder path = new StringBuilder();
public List<String> generateParenthesis(int n) {
backtrack(n, 0, 0);
return res;
}
public void backtrack(int n, int l, int r){ //括号对数,左括号数量,右括号数量
if(l==n && r==n){ // 左右括号数量都为n时说明可以加入res
res.add(path.toString());
return;
}
if(l<n){ // 左括号数小于n时可以添加左括号
path.append('(');
backtrack(n, l+1, r);
path.deleteCharAt(path.length()-1);
}
if(r<l){ // 右括号数小于左括号时可以添加右括号
path.append(')');
backtrack(n, l, r+1);
path.deleteCharAt(path.length()-1);
}
}
题39 组合总和
链接: link
同一集合中取元素的组合问题(可以重复取)。注意这里backtrack(candidates, target, i)。
List<List<Integer>> res = new ArrayList<>(); // 存放结果
LinkedList<Integer> path = new LinkedList<>(); // 存放路径
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtrack(candidates, target, 0);
return res;
}
public void backtrack(int[] candidates, int target, int startIndex){
if(target==0){ // target刚好减为0时说明满足题意
res.add(new ArrayList<>(path));
return;
}
for(int i=startIndex; i<candidates.length && target>=0; i++){ // 这边一定要有target>=0的条件
path.add(candidates[i]);
target -= candidates[i];
backtrack(candidates, target, i); // 可以重复取,所以还是i
target += candidates[i];
path.removeLast();
}
}
题46 全排列
链接: link
同一集合中取元素的排列问题。注意排列问题要对路径上元素去重。
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
backtrack(nums);
return res;
}
public void backtrack(int[] nums){
if(path.size()==nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i=0; i<nums.length; i++){ // 排列问题每次都从下标0开始取
if(!path.contains(nums[i])){ // 但对路径上元素要去重
path.add(nums[i]);
backtrack(nums);
path.removeLast();
}
}
}
题78 子集
链接: link
同一集合中取元素的子集问题。注意子集问题是找到所有的(包含非叶子结点)路径,不需要return。
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> subsets(int[] nums) {
backtrack(nums, 0);
return res;
}
public void backtrack(int[] nums, int startIndex){
res.add(new ArrayList<>(path)); // 注意这里不需要return
for(int i=startIndex; i<nums.length; i++){
path.add(nums[i]);
backtrack(nums, i+1);
path.removeLast();
}
}
题79 单词搜索
链接: link
本题需要在主方法里对每个位置作为起点调用回溯方法,找到一个即可返回true。
public boolean exist(char[][] board, String word) {
for(int i=0; i<board.length; i++){
for(int j=0; j<board[0].length; j++){ // 遍历网格中每一个起始位置
if(backtrack(board, word, 0, i, j)){ // 只要有一处返回true就说明可以找到单词
return true;
}
}
}
return false;
}
public boolean backtrack(char[][] board, String word, int startIndex, int i, int j){
if(!isValid(board, i, j) || board[i][j] != word.charAt(startIndex) || board[i][j]=='.'){
return false; // 位置越界或者字符不同或者已经被使用过
}
if(startIndex==word.length()-1){
return true; // 说明找到了单词
}
char temp = board[i][j];
board[i][j] = '.'; // 标记该字符已经被使用过
boolean b = backtrack(board, word, startIndex+1, i-1, j) || backtrack(board, word, startIndex+1, i+1, j) ||
backtrack(board, word, startIndex+1, i, j-1) || backtrack(board, word, startIndex+1, i, j+1);
board[i][j] = temp; // 回溯
return b;
}
public boolean isValid(char[][] board, int i, int j){ // 判断位置是否越界
if(i>=0 && i<board.length && j>=0 && j<board[0].length) return true;
return false;
}
题301 删除无效的括号
链接: link
关键在于求出要删除的左右括号数。有一个细节,本题因为有字母存在,所以不能if(左括号)…else…的形式来判断,而要写成if(左括号)…else if(右括号)…的判断。
List<String> res = new ArrayList<>();
public List<String> removeInvalidParentheses(String s) {
int removeLeft=0, removeRight=0; // 要删除的左右括号数
for(int i=0; i<s.length(); i++){
if(s.charAt(i)=='(') removeLeft++; // 左括号加1
else if (s.charAt(i)==')'){
if(removeLeft==0) removeRight++; // 要删除的左括号为0才会让右括号加1
else removeLeft--; // 不然都是让左括号减1
}
}
backtrack(s, 0, removeLeft, removeRight);
return res;
}
public void backtrack(String str, int startIndex, int removeLeft, int removeRight){
if(removeLeft==0 && removeRight==0 && isValid(str)){
res.add(str); // 要删除的左右括号数都为0且当前字符串合法,就添加结果
return;
}
for(int i=startIndex; i<str.length(); i++){
if(i!=startIndex && str.charAt(i)==str.charAt(i-1)){
continue; // 跳过重复元素,剪枝操作
}
if(removeLeft+removeRight > str.length()-i){
return; // 剩余元素仍不够待删除数量,剪枝操作
}
if(str.charAt(i)=='(' && removeLeft>0){ // 尝试删除一个左括号
backtrack(str.substring(0,i)+str.substring(i+1), i, removeLeft-1, removeRight);
}
if(str.charAt(i)==')' && removeRight>0){ // 尝试删除一个右括号
backtrack(str.substring(0,i)+str.substring(i+1), i, removeLeft, removeRight-1);
}
}
}
public boolean isValid(String str){ // 判断字符串是否合法
int count = 0;
for(int i=0; i<str.length(); i++){
if(str.charAt(i)=='(') count++; // 左括号就数量加1
else if (str.charAt(i)==')'){ // 右括号就数量减1
count--;
if(count<0) return false; // 数量为负就说明不合法
}
}
return count==0;
}