You can split the notes string into words and then explode the words. Finally count the number of notes for each person_id, category and occurence of word per person_id, category. If they counts are equal then construct the words by collect_list.
from pyspark.sql import functions as F
from pyspark.sql import Window
data = [(1, "A", "He bought cat"),
(1, "A", "He bought dog"),
(1, "B", "He has hen"),
(2, "A", "Switzerland Australia"),
(2, "A", "Australia"),]
df = spark.createDataFrame(data, ("person_id", "category", "notes", ))
window_spec_category = Window.partitionBy("person_id", "category")
df_word = df.withColumn("category_count", F.count("*").over(window_spec_category))\
.select("person_id", "category", F.posexplode(F.split(F.col("notes"), " ")).alias("pos", "word"))
window_spec_word = Window.partitionBy("person_id", "category", "word")
matching_words = df_word.withColumn("word_count", F.count("*").over(window_spec_word))\
.withColumn("rn", F.row_number().over(window_spec_word.orderBy(F.lit(None))))\
.filter(F.col("word_count") == F.col("category_count"))\
.filter(F.col("rn") == F.lit(1))\
.drop("rn")
window_spec_collect = window_spec_category.orderBy("pos").rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)
matching_words.withColumn("result", F.concat_ws(" ", F.collect_list("word").over(window_spec_collect)))\
.withColumn("rn", F.row_number().over(window_spec_category.orderBy(F.lit(None))))\
.filter(F.col("rn") == F.lit(1))\
.select("person_id", "category", "result")\
.show()
Output
+---------+--------+----------+
|person_id|category| result|
+---------+--------+----------+
| 1| A| He bought|
| 1| B|He has hen|
| 2| A| Australia|
+---------+--------+----------+