最长的顺子

一、题目描述

斗地主起源于湖北十堰房县,据说是一位叫吴修全的年轻人根据当地流行的扑克玩法“跑得快”改编的,如今已风靡整个中国,并流行于互联网上。

牌型:单顺,又称顺子,最少5张牌,最多12张牌(3…A)不能有2,也不能有大小王,不计花色。

例如: 3-4-5-6-7-8,7-8-9-10-J-Q,3-4-5-6-7-8-9-10-J-Q-K-A

可用的牌 3<4<5<6<7<8<9<10<J<Q<K<A<2<B(小王)<C(大王),每种牌除大小王外有四种花色

(共有13×4+2张牌)

二、输入输出描述

输入描述
  • 第一行:当前手中的牌(字符串,用 - 分隔)
  • 第二行:已经出过的牌(字符串,用 - 分隔)
输出描述
  • 最长的顺子序列(按从小到大顺序,用 - 分隔);
  • 若无合法顺子,则输出NO-CHAIN。

三、示例

输入

3-3-3-3-4-4-5-5-6-7-8-9-10-J-Q-K-A
4-5-6-7-8-8-8

输出9-10-J-Q-K-A
说明

 

四、解题思路

1. 核心思想

采用数量统计 + 连续遍历验证的策略:

  • 数量统计:先通过 “总牌数 - 手中牌 - 已出牌” 计算剩余牌的数量,明确哪些牌可用于组顺子;
  • 连续遍历:从顺子的所有可能起始位置(3 到 10)出发,向右遍历连续牌面,验证是否能组成≥5 张的顺子,最终保留最长的那个。
2. 问题本质分析

这个问题的核心是 **“找连续序列的最长有效子串”**:

  • 顺子的本质是 “牌面值连续且每张牌至少有 1 张剩余”;
  • 顺子的约束条件:① 仅包含 3、4、5、6、7、8、9、10、J、Q、K、A(索引 3-14);② 长度≥5;③ 找最长的;
  • 非顺子牌(2、B、C)无需参与遍历,可直接排除,减少计算量。
3. 核心逻辑
  1. 映射初始化:建立 “牌面值 - 索引” 双向映射,方便快速操作剩余牌数数组;
  2. 剩余牌计算:通过总牌数减去手中牌、已出牌,得到每张牌的剩余数量;
  3. 顺子查找:
    • 遍历所有可能的顺子起始位置(3 到 10,因顺子至少 5 张,起始超过 10 则无法凑够 5 张);
    • 从起始位置向右遍历连续牌面,收集可组成顺子的牌;
    • 若遍历中断(某牌无剩余),验证当前顺子是否符合条件,更新最长顺子;
  4. 结果返回:返回最长顺子,无则返回NO-CHAIN
4. 步骤拆解
  1. 输入处理:读取手中牌、已出牌,按-分割为数组;
  2. 映射与初始化
    • 构建牌面值↔索引的双向映射;
    • 初始化 count 数组为所有牌的总数量;
  3. 计算剩余牌数
    • 遍历手中牌,对应 count 数组位置数量 - 1;
    • 遍历已出牌,对应 count 数组位置数量 - 1;
  4. 查找最长顺子
    • 初始化答案为NO-CHAIN,最长长度为 0;
    • 遍历顺子起始位置l(3 到 10):
      • l开始向右遍历r(到 15 为止),若count[r]≥1则加入当前顺子;
      • count[r]=0,则验证当前顺子长度≥5 且≥最长长度,若是则更新答案;
      • 中断当前遍历,左边界跳转到r,继续下一个起始位置;
    • 左边界l自增,重复上述步骤;
  5. 输出结果:返回最长顺子(或NO-CHAIN)。

五、代码实现

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import java.util.StringJoiner;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    String[] my = sc.nextLine().split("-");
    String[] used = sc.nextLine().split("-");

    System.out.println(getResult(my, used));
  }

  public static String getResult(String[] my, String[] used) {
    // 牌面值 映射为 count列表索引值
    HashMap<String, Integer> mapToV = new HashMap<>();
    mapToV.put("3", 3);
    mapToV.put("4", 4);
    mapToV.put("5", 5);
    mapToV.put("6", 6);
    mapToV.put("7", 7);
    mapToV.put("8", 8);
    mapToV.put("9", 9);
    mapToV.put("10", 10);
    mapToV.put("J", 11);
    mapToV.put("Q", 12);
    mapToV.put("K", 13);
    mapToV.put("A", 14);
    mapToV.put("2", 16);
    mapToV.put("B", 17);
    mapToV.put("C", 18);

    // count每个索引值对应一个牌面值,count元素值就是对应牌面的数量
    // 牌面值             3  4  5  6  7  8  9  10 J  Q  K  A     2  B  C
    // 索引值             3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18
    int[] count = {0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 1, 1};

    // count列表索引值 隐射为 牌面值
    HashMap<Integer, String> mapToK = new HashMap<>();
    mapToK.put(3, "3");
    mapToK.put(4, "4");
    mapToK.put(5, "5");
    mapToK.put(6, "6");
    mapToK.put(7, "7");
    mapToK.put(8, "8");
    mapToK.put(9, "9");
    mapToK.put(10, "10");
    mapToK.put(11, "J");
    mapToK.put(12, "Q");
    mapToK.put(13, "K");
    mapToK.put(14, "A");
    mapToK.put(16, "2");
    mapToK.put(17, "B");
    mapToK.put(18, "C");

    // 总牌数 减去 自己手中牌数
    for (String k : my) {
      count[mapToV.get(k)] -= 1;
    }

    // 总牌数 减去 已打出去的牌数
    for (String k : used) {
      count[mapToV.get(k)] -= 1;
    }

    String ans = "NO-CHAIN";
    int maxLen = 0;

    // l为顺子的左边界,[3,10],即顺子的左边界值最少是count索引3,最多是count索引10
    int l = 3;
    while (l <= 10) {
      ArrayList<String> tmp = new ArrayList<>();
      StringJoiner sj = new StringJoiner("-");
      for (int r = l; r < 16; r++) {
        // 如果对应牌数>=1,则可以组顺子
        if (count[r] >= 1) {
          tmp.add(mapToK.get(r));
          sj.add(mapToK.get(r));
        } else {
          // 如果对应牌数 == 0,则顺子中断
          // 顺子必须大于五张牌,且总是记录最长,遇到长度相同的,记录后面发现的顺子
          if (tmp.size() >= 5 && tmp.size() >= maxLen) {
            maxLen = tmp.size();
            ans = sj.toString();
          }
          // 顺子中断处+1,即为下一次顺子的起始位置
          l = r;
          break;
        }
      }
      l++;
    }

    return ans;
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值