锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Java 编程问题:一、字符串、数字和数学

时间:2023-08-05 01:37:00 连接器df1ecdf37nc连接器

原文:Java Coding Problems

协议:CC BY-NC-SA 4.0

贡献者:飞龙

本文来自【ApacheCN Java 谷歌翻译用谷歌翻译。

本章包括 39 一个涉及字符串、数字和数学操作的问题。我们将从计算重复项、系列经典问题,如计算重复项、经典问题开始。然后,我们将研究专门用于数字和数学操作的问题,如两个大数求和和和和操作溢出,比较两个无符号,以及计算除法和模具的下限。每个问题都有几个解决方案,包括 Java8 函数风格。另外,我们将讨论和讨论 JDK9、10、11 和 12 相关问题。

在本章的结尾,您将知道如何使用一系列技术,以便您可以操作字符串并应用它们,并调整它们以适应许多其他问题。您还将知道如何解决可能导致奇怪和不可预测结果的数学角落。

问题

使用以下问题来测试您的字符串操作和数学角编程能力。我强烈建议您在使用解决方案和下载示例程序之前尝试每个问题:

  1. 重复字符计数:编写一个计数给定字符串中重复字符的程序。

  2. 寻找第一个非重复字符:编写程序,返回给定字符串中的第一个非重复字符。

  3. 反转字母和单词:编写反转每个单词字母的程序,以及反转每个单词字母和单词本身的程序。

  4. 检查字符串是否包含数字:编写程序检查给定字符串是否只包含数字。

  5. 计数元音和辅音:编写一个计算给定字符串中元音和辅音数量的程序。英语有五个元音(a、e、i、o 和 u)。

  6. 计数一个字符的出现次数:编写一个程序,计算给定字符串中某个字符的出现次数。

  7. String转换成intlongfloatdouble:编写个程序,给定String对象(代表数字)转换为intlongfloatdouble

  8. 删除字符串中的空格:编写程序,删除给定字符串中的所有空间。

  9. 用分隔符连接多个字符串:编写程序,用给定的分隔符连接给定的字符串。

  10. 产生所有的排列:编写一个程序,生成给定字符串的所有排列。

  11. 检查字符串是否回文:编写程序,确定给定的字符串是否为回文。

  12. 删除重复字符:从给定给定字符串中删除重复字符的程序。

  13. 删除给定字符:编写一个从给定字符串中删除给定字符的程序。

  14. 找出最常见的字符:编写一个程序,在给定的字符串中找到最常见的字符。

  15. 字符串数组按长度排序:编写按给定字符串数组长度排序的程序。

  16. 检查字符串是否包含子字符串:编写程序,检查给定字符串是否包含给定子字符串。

  17. 计算子串出现在字符串中的次数:编写一个程序,计算给定字符串在另一个给定字符串中的次数。

  18. 检查是否有两个字符串:编写一个程序来检查两个字符串是否是异序词。假设一个字符串的异序词是字符串的排列,忽略了大小写和空间。

  19. 声明多行字符串(文本块):编写声明多行字符串或文本块的程序。

  20. 连接同一字符串n:编写一个程序,将相同的字符串连接到给定的次数。

  21. 删除前导和跟随空间:编写一个程序,删除给定字符串的前导和跟间。

  22. 寻找最长的公共前缀:编写一个程序,找到给定字符串的最长公共前缀。

  23. 应用缩进:编写几个代码片段,缩进给定的文本应用程序。

  24. 转换字符串:写几段代码,将一个字符串转换成另一个字符串。

  25. 计算两个数的最小值和最大值:编写一个程序,返回两个数的最小值和最大值。

  26. 两个大int/long数量和计算溢出:写一个程序,两个大的int/long计算溢出时抛出计算异常。

  27. 字符串作为基数编写程序,将给定字符串分析为给定基数中的无符号(intlong)。

  28. 转换无符号数字:编写个程序,给定int数字无符号转换成long

  29. 比较两个无符号数字:编写一个程序,比较给定的两个数字作为无符号数字。

  30. 除法和模具无符号值:编写一个,计算给定无符号值的除法和模具。

  31. double/float浮点值是否有限:编写一个程序来确定给定double/float值是否为有限浮点值?

  32. 两个布尔表达式应用逻辑 AND/OR/XOR:为两个布尔表达式应用逻辑编写一个程序 AND/OR/XOR。

  33. BigInteger转换为原始类型:从给定的BigInteger中提取原始类型值。

  34. long转换成int:编写一个将long转换成int的程序。

  35. 计算下限除法和模:编写程序,计算给定除法(x)和除法(y)下限除法和下限模。

  36. 下一个浮点值:编写程序并返回给定的程序float/double下一个浮点值相邻于正负无限方向。

  37. 两个大int/long值乘运算溢出:写一个程序,两个大的int/long值相乘,运算溢出时抛出算术异常。

  38. 融合乘加FMA):写一个程序,拿三个float/double值(abc),高效计算ab c*。

  39. 紧凑的数字格式化:编写一个程序,编写数字 1000000 格式化为 1M和 1ML(意大利)。此外,将字符串中的字符 1M 和 1MLN 分析为数字。

解决方案

以下部分介绍了上述问题的解决方案。请记住,通常没有正确的方法来解决特定的问题。此外,请记住,这里显示的解释只包括解决问题所需的最有趣和最重要的细节。您可以从此页面下载示例解决方案,查看更多详细信息并尝试程序。

1 重复字符计数

计算字符串中的字符(包括特殊字符,如#、$和%)解决方案意味着取每个字符并与其他字符进行比较。在比较过程中,通过数字计数器保持计数状态。每次找到当前字符,都会增加一个计数器。

解决这个问题有两种方法。

第一种解决方案迭代字符串,并使用Map存储字符作为键,存储出现的次数作为值。当前字符从未添加到Map,则将其添加为(character, 1)。如果当前字符存在于Map中,则只需将其出现次数增加 1,例如(character, occurrences+1)。如下代码所示:

public Map<Character, Integer> countDuplicateCharacters(String str) { 
        

  Map<Character, Integer> result = new HashMap<>();

  // or use for(char ch: str.toCharArray()) { ... }
  for (int i = 0; i<str.length(); i++) { 
        
    char ch = str.charAt(i); 

    result.compute(ch, (k, v) -> (v == null) ? 1 : ++v);
  }

  return result;
}

另一个解决方案依赖于 Java8 的流特性。这个解决方案有三个步骤。前两步是将给定的字符串转换成Stream,最后一步是对字符进行分组和计数。步骤如下:

  1. 对原始字符串调用String.chars()方法。这将返回IntStream。这个IntStream包含给定字符串中字符的整数表示。
  2. 通过mapToObj()方法将IntStream转换成字符流(将整数表示转换成人性化的字符形式)。
  3. 最后,将字符分组(Collectors.groupingBy())并计数(Collectors.counting())。

以下代码片段将这三个步骤粘在一个方法中:

public Map<Character, Long> countDuplicateCharacters(String str) { 
        

  Map<Character, Long> result = str.chars()
    .mapToObj(c -> (char) c)
    .collect(Collectors.groupingBy(c -> c, Collectors.counting()));

  return result;
}

Unicode 字符呢?

我们非常熟悉 ASCII 字符。我们有 0-31 之间的不可打印控制码,32-127 之间的可打印字符,128-255 之间的扩展 ASCII 码。但是 Unicode 字符呢?对于每个需要操作 Unicode 字符的问题,请考虑本节

因此,简而言之,早期的 Unicode 版本包含值小于 65535(0xFFFF)的字符。Java 使用 16 位char数据类型表示这些字符。只要i不超过 65535,调用charAt(i)就可以正常工作。但随着时间的推移,Unicode 添加了更多字符,最大值达到了 1114111(0x10FFFF)。这些字符不适合 16 位,因此 UTF-32 编码方案考虑 32 位值(称为码位)。

不幸的是,Java 不支持 UTF-32!尽管如此,Unicode 还是提出了一个解决方案,仍然使用 16 位来表示这些字符。此解决方案意味着:

  • 16 位高位代理:1024 个值(U+D800 到 U+DBFF)
  • 16 位低位代理:1024 个值(U+DC00 到 U+DFFF)

现在,一个高代理后跟一个低代理定义了所谓的代理对。代理项对用于表示 65536(0x10000)和 1114111(0x10FFFF)之间的值。因此,某些字符(称为 Unicode 补充字符)被表示为 Unicode 代理项对(一个字符(符号)适合于一对字符的空间),这些代理项对被合并到一个代码点中。Java 利用了这种表示,并通过一组方法来公开它,如codePointAt()codePoints()codePointCount(),和offsetByCodePoints()(请查看 Java 文档了解详细信息)。调用codePointAt()而不是charAt()codePoints()而不是chars()等等,可以帮助我们编写覆盖 ASCII 和 Unicode 字符的解决方案。

例如,众所周知的双心符号是 Unicode 代理项对,可以表示为包含两个值的char[]\uD83D\uDC95。此符号的码位为128149。要从此代码点获取一个String对象,请调用String str = String.valueOf(Character.toChars(128149))str中的码点计数可以通过调用str.codePointCount(0, str.length())来完成,即使str长度为 2,也返回 1;调用str.codePointAt(0)返回128149,调用str.codePointAt(1)返回56469。调用Character.toChars(128149)返回 2,因为需要两个字符来表示此代码点是 Unicode 代理项对。对于 ASCII 和 Unicode 16 位字符,它将返回 1。

因此,如果我们尝试重写第一个解决方案(迭代字符串并使用Map将字符存储为键,将出现次数存储为值)以支持 ASCII 和 Unicode(包括代理项对),我们将获得以下代码:

public static Map<String, Integer> 
    countDuplicateCharacters(String str) { 
        

  Map<String, Integer> result = new HashMap<>();

  for (int i = 0; i < str.length(); i++) { 
        
 int cp = str.codePointAt(i);
 String ch = String.valueOf(Character.toChars(cp));
 if(Character.charCount(cp) == 2) { 
         // 2 means a surrogate pair
 i++;
 }

    result.compute(ch, (k, v) -> (v == null) ? 1 : ++v);
  }

  return result;
}

突出显示的代码也可以编写如下:

String ch = String.valueOf(Character.toChars(str.codePointAt(i)));
if (i < str.length() - 1 && str.codePointCount(i, i + 2) == 1) { 
        
  i++;
}

最后,尝试重写 Java8 函数式解决方案以覆盖 Unicode 代理项对,可以执行以下操作:

public static Map<String, Long> countDuplicateCharacters(String str) { 
         

  Map<String, Long> result = str.codePoints()
    .mapToObj(c -> String.valueOf(Character.toChars(c)))
    .collect(Collectors.groupingBy(c -> c, Collectors.counting()));

  return result;
}

对于第三方库支持,请考虑 Guava:Multiset

下面的一些问题将提供包括 ASCII、16 位 Unicode 和 Unicode 代理项对的解决方案。它们是任意选择的,因此,通过依赖这些解决方案,您可以轻松地为没有提供此类解决方案的问题编写解决方案。

2 找到第一个非重复字符

找到字符串中第一个不重复的字符有不同的解决方案。主要地,这个问题可以通过字符串的一次遍历或更完整/部分的遍历来解决。

在单遍历方法中,我们填充一个数组,该数组用于存储字符串中恰好出现一次的所有字符的索引。使用此数组,只需返回包含非重复字符的最小索引:

private static final int EXTENDED_ASCII_CODES = 256;
...
public char firstNonRepeatedCharacter(String str) { 
        

  int[] flags = new int[EXTENDED_ASCII_CODES];

  for (int i = 0; i < flags.length; i++) { 
        
    flags[i] = -1;
  }

  for (int i = 0; i < str.length(); i++) { 
        
    char ch = str.charAt(i);
    if (flags[ch] == -1) { 
        
      flags[ch] = i;
    } else { 
        
      flags[ch] = -2;
    }
  }

  int position = Integer.MAX_VALUE;

  for (int i = 0; i < EXTENDED_ASCII_CODES; i++) { 
        
    if (flags[i] >= 0) { 
        
      position = Math.min(position, flags[i]);
    }
  }

  return position == Integer.MAX_VALUE ?
    Character.MIN_VALUE : str.charAt(position);
}

此解决方案假定字符串中的每个字符都是扩展 ASCII 表(256 个代码)的一部分。如果代码大于 256,则需要相应地增加数组的大小。只要数组大小不超过char类型的最大值,即Character.MAX_VALUE,即 65535,该解决方案就可以工作。另一方面,Character.MAX_CODE_POINT返回 Unicode 码位的最大值 1114111。为了覆盖这个范围,我们需要另一个基于codePointAt()codePoints()的实现

由于采用了单次遍历的方法,所以速度非常快。另一种解决方案是循环每个字符的字符串并计算出现的次数。每出现一次(重复)就会打断循环,跳到下一个字符,并重复算法。如果到达字符串的结尾,则返回当前字符作为第一个不可重复的字符。本书附带的代码中提供了此解决方案。

这里介绍的另一个解决方案依赖于LinkedHashMap。这个 Java 映射是一个插入顺序映射(它保持了键插入到映射中的顺序),对于这个解决方案非常方便。LinkedHashMap以字符作为键,以出现次数作为值填充。一旦LinkedHashMap完成,它将返回值等于 1 的第一个键。由于插入顺序功能,这是字符串中第一个不可重复的字符:

public char firstNonRepeatedCharacter(String str) { 
        

  Map<Character, Integer> chars = new LinkedHashMap<>();

  // or use for(char ch: str.toCharArray()) { ... }
  for (int i = 0; i < str.length(); i++) { 
        
    char ch = str.charAt(i);

    chars.compute(ch, (k, v) -> (v == null) ? 1 : ++v);
  }

  for (Map.Entry<Character, Integer> entry: chars.entrySet()) { 
        
    if (entry.getValue() == 1) { 
        
      return entry.getKey();
    }
  }

  return Character.MIN_VALUE;
}

在本书附带的代码中,前面提到的解决方案是用 Java8 函数式编写的。此外,支持 ASCII、16 位 Unicode 和 Unicode 代理项对的函数式解决方案如下:

public static String firstNonRepeatedCharacter(String str) { 
        

  Map<Integer, Long> chs = str.codePoints()
    .mapToObj(cp -> cp)
    .collect(Collectors.groupingBy(Function.identity(),
      LinkedHashMap::new, Collectors.counting()));

  int cp = chs.entrySet().stream()
   .filter(e -> e.getValue() == 1L)
   .findFirst()
   .map(Map.Entry::getKey)
   .orElse(Integer.valueOf(Character.MIN_VALUE));

  return String.valueOf(Character.toChars(cp));
}

要更详细地理解这段代码,请考虑“Unicode 字符是什么?计数重复字符”部分的小节

3 反转字母和单词

首先,让我们只反转每个单词的字母。这个问题的解决方案可以利用StringBuilder类。第一步包括使用空格作为分隔符(Spring.split(" "))将字符串拆分为单词数组。此外,我们使用相应的 ASCII 码反转每个单词,并将结果附加到StringBuilder。首先,我们将给定的字符串按空格拆分。然后,我们循环得到的单词数组,并通过charAt()按相反的顺序获取每个字符来反转每个单词:

private static final String WHITESPACE = " ";
...
public String reverseWords(String str) { 
        

 String[] words = str.split(WHITESPACE);
 StringBuilder reversedString = new StringBuilder();

 for (String word: words) { 
        
   StringBuilder reverseWord = new StringBuilder();

   for (int i = word.length() - 1; i >= 0; i--) { 
        
     reverseWord.append(word.charAt(i));
   }

   reversedString.append(reverseWord).append(WHITESPACE);
 }

 return reversedString.toString();
}

在 Java8 函数样式中获得相同的结果可以如下所示:

private static final Pattern PATTERN = Pattern.compile(" +");
...
public static String reverseWords(String str) { 
        

  return PATTERN.splitAsStream(str)
    .map(w -> new StringBuilder(w).reverse())
    .collect(Collectors.joining(" "));
}

请注意,前面两个方法返回一个字符串,其中包含每个单词的字母,但单词本身的初始顺序相同。现在,让我们考虑另一种方法,它反转每个单词的字母和单词本身。由于内置的StringBuilder.reverse()方法,这非常容易实现:

public String reverse(String str) { 
        

  return new StringBuilder(str).reverse().toString();
}

对于第三方库支持,请考虑 ApacheCommonsLang,StringUtils.reverse()

4 检查字符串是否只包含数字

这个问题的解决依赖于Character.isDigit()String.matches()方法

依赖于Character.isDigit()的解决方案是非常简单和快速地循环字符串,如果此方法返回false,则中断循环:

public static boolean containsOnlyDigits(String str) { 
        

  for (int i = 0; i < str.length(); i++) { 
        
    if (!Character.isDigit(str.charAt(i))) { 
        
      return false;
    }
  }

  return true;
}

在 Java8 函数式中,前面的代码可以使用anyMatch()重写:

public static boolean containsOnlyDigits(String str) { 
        

  return !str.chars()
    .anyMatch(n -> !Character.isDigit(n));
}

另一种解决方案依赖于String.matches()。此方法返回一个boolean值,指示此字符串是否与给定的正则表达式匹配:

public static boolean containsOnlyDigits(String str) { 
        

  return str.matches("[0-9]+");
}

请注意,Java8 函数式和基于正则表达式的解决方案通常比较慢,因此如果速度是一个要求,那么最好使用第一个使用Character.isDigit()的解决方案。

避免通过parseInt()parseLong()解决此问题。首先,捕捉NumberFormatException并在catch块中进行业务逻辑决策是不好的做法。其次,这些方法验证字符串是否为有效数字,而不是仅包含数字(例如,-4 有效)。
对于第三方库支持,请考虑 ApacheCommonsLang,StringUtils.isNumeric()

5 元音和辅音计数

以下代码适用于英语,但取决于您所涵盖的语言种类,元音和辅音的数量可能会有所不同,因此应相应调整代码。

此问题的第一个解决方案需要遍历字符串并执行以下操作:

  1. 我们需要检查当前字符是否是元音(这很方便,因为英语中只有五个纯元音;其他语言有更多元音,但数量仍然很小)。
  2. 如果当前字符不是元音,则检查它是否位于'a''z'之间(这意味着当前字符是辅音)。

注意,最初,给定的String对象被转换为小写。这有助于避免与大写字符进行比较。例如,仅对'a'进行比较,而不是对'A''a'进行比较。

此解决方案的代码如下:

private static final Set<Character> allVowels
            = new HashSet(Arrays.asList('a', 'e', 'i', 'o', 'u'));

public static Pair<Integer, Integer> 
    countVowelsAndConsonants(String str) { 
        

  str = str.toLowerCase();
  int vowels = 0;
  int consonants = 0;

  for (int i = 0; i < str.length(); i++) { 
        
    char ch = str.charAt(i);
    if (allVowels.contains(ch)) { 
        
      vowels++;
    } else if ((ch >= 'a' && ch <= 'z')) { 
        
      consonants++;
    }
  }

  return Pair.of(vowels, consonants);
}

在 Java8 函数式中,可以使用chars()filter()重写此代码:

private static final Set<Character> allVowels
            元器件数据手册IC替代型号,打造电子元器件IC百科大全!
          

相关文章